From bd02675ae18cf9931180490fe99a2cf638280608 Mon Sep 17 00:00:00 2001 From: Tanner Gooding Date: Thu, 15 Jun 2023 09:07:18 -0700 Subject: [PATCH 1/6] Updating Math.Max and Math.Min to utilize AVX512 when available --- src/coreclr/jit/importercalls.cpp | 154 +++++++++++++++++++++++++++++- 1 file changed, 150 insertions(+), 4 deletions(-) diff --git a/src/coreclr/jit/importercalls.cpp b/src/coreclr/jit/importercalls.cpp index fef3adb2377903..5f4df1618e793e 100644 --- a/src/coreclr/jit/importercalls.cpp +++ b/src/coreclr/jit/importercalls.cpp @@ -3398,6 +3398,7 @@ GenTree* Compiler::impIntrinsic(GenTree* newobjThis, retNode = impMathIntrinsic(method, sig, callType, ni, tailCall); break; } + #if defined(TARGET_ARM64) // ARM64 has fmax/fmin which are IEEE754:2019 minimum/maximum compatible case NI_System_Math_Max: @@ -3430,7 +3431,87 @@ GenTree* Compiler::impIntrinsic(GenTree* newobjThis, if (cnsNode == nullptr) { - // no constant node, nothing to do + if (!compOpportunisticallyDependsOn(InstructionSet_AVX512DQ)) + { + // no constant node and no AVX512DQ.VL support, nothing to do + break; + } + + // We are constructing a chain of intrinsics similar to: + // var op1 = Vector128.CreateScalarUnsafe(x); + // var op2 = Vector128.CreateScalarUnsafe(y); + // + // var tmp = Avx512DQ.RangeScalar(op1, op2, imm8); + // var tbl = Vector128.CreateScalarUnsafe(0x00); + // + // tmp = Avx512F.FixupScalar(tmp, op2, tbl, 0x00); + // tmp = Avx512F.FixupScalar(tmp, op1, tbl, 0x00); + // + // return tmp.ToScalar(); + + // RangeScalar operates by default almost as MaxNumber or MinNumber + // but, it propagates sNaN and does not propagate qNaN. So we need + // an additional fixup to ensure we propagate qNaN as well. + + uint8_t imm8; + + if (ni == NI_System_Math_Max) + { + // 0b01_00: Sign(CompareResult), MaxValue + imm8 = 0x04; + } + else + { + assert(ni == NI_System_Math_Min); + + // 0b01_01: Sign(CompareResult), MinValue + imm8 = 0x05; + } + + GenTree* op3 = gtNewIconNode(imm8); + GenTree* op2 = gtNewSimdCreateScalarUnsafeNode(TYP_SIMD16, impPopStack().val, callJitType, 16); + GenTree* op1 = gtNewSimdCreateScalarUnsafeNode(TYP_SIMD16, impPopStack().val, callJitType, 16); + + GenTree* op2Clone; + op2 = + impCloneExpr(op2, &op2Clone, CHECK_SPILL_ALL, nullptr DEBUGARG("Cloning op2 for Math.Max/Min")); + + GenTree* op1Clone; + op1 = + impCloneExpr(op1, &op1Clone, CHECK_SPILL_ALL, nullptr DEBUGARG("Cloning op1 for Math.Max/Min")); + + GenTree* tmp = + gtNewSimdHWIntrinsicNode(TYP_SIMD16, op1, op2, op3, NI_AVX512DQ_RangeScalar, callJitType, 16); + + GenTreeVecCon* tbl = gtNewVconNode(TYP_SIMD16); + + // FixupScalar(left, right, table, control) computes the input type of right + // adjusts it based on the table and then returns + // + // In our case, left is going to be the result of the RangeScalar operation + // and right is going to be op1 or op2. In the case op1/op2 is QNaN or SNaN + // we want to preserve it instead. Otherwise we want to preserve the original + // result computed by RangeScalar. + // + // If both inputs are NaN, then we'll end up taking op1 by virtue of it being + // the latter fixup. + // + // QNAN: 0b0001: Preserve right + // SNAN: 0b0001 + // ZERO: 0b0000: Preserve left + // +ONE: 0b0000 + // -INF: 0b0000 + // +INF: 0b0000 + // -VAL: 0b0000 + // +VAL: 0b0000 + tbl->gtSimdVal.i32[0] = 0x11; + + tmp = gtNewSimdHWIntrinsicNode(TYP_SIMD16, tmp, op2Clone, tbl, NI_AVX512F_FixupScalar, callJitType, + 16); + tmp = gtNewSimdHWIntrinsicNode(TYP_SIMD16, tmp, op1Clone, tbl, NI_AVX512F_FixupScalar, callJitType, + 16); + + retNode = gtNewSimdHWIntrinsicNode(callType, tmp, NI_Vector128_ToScalar, callJitType, 16); break; } @@ -3481,6 +3562,8 @@ GenTree* Compiler::impIntrinsic(GenTree* newobjThis, break; } + bool needsFixup = false; + if (ni == NI_System_Math_Max) { // maxsd, maxss return op2 if both inputs are 0 of either sign @@ -3488,10 +3571,17 @@ GenTree* Compiler::impIntrinsic(GenTree* newobjThis, // the known constant is +0. This is because if the unknown value // is -0, we'd need the cns to be op2. But if the unknown value // is NaN, we'd need the cns to be op1 instead. + // + // However, if AVX512DQ is supported we have access to vfixupimmsd + // and vfixupimmss. This can be used to account for +0 vs -0. if (cnsNode->IsFloatPositiveZero()) { - break; + if (!compOpportunisticallyDependsOn(InstructionSet_AVX512F)) + { + break; + } + needsFixup = true; } // Given the checks, op1 can safely be the cns and op2 the other node @@ -3515,7 +3605,11 @@ GenTree* Compiler::impIntrinsic(GenTree* newobjThis, if (cnsNode->IsFloatNegativeZero()) { - break; + if (!compOpportunisticallyDependsOn(InstructionSet_AVX512F)) + { + break; + } + needsFixup = true; } // Given the checks, op1 can safely be the cns and op2 the other node @@ -3548,8 +3642,60 @@ GenTree* Compiler::impIntrinsic(GenTree* newobjThis, op2 = gtNewSimdCreateScalarUnsafeNode(TYP_SIMD16, op2, callJitType, 16); retNode = gtNewSimdHWIntrinsicNode(TYP_SIMD16, op1, op2, ni, callJitType, 16); - retNode = gtNewSimdHWIntrinsicNode(callType, retNode, NI_Vector128_ToScalar, callJitType, 16); + if (needsFixup) + { + GenTree* op2Clone; + op2 = + impCloneExpr(op2, &op2Clone, CHECK_SPILL_ALL, nullptr DEBUGARG("Cloning op2 for Math.Max/Min")); + + retNode->AsHWIntrinsic()->Op(2) = op2; + + GenTreeVecCon* tbl = gtNewVconNode(TYP_SIMD16); + + // FixupScalar(left, right, table, control) computes the input type of right + // adjusts it based on the table and then returns + // + // In our case, left is going to be the result of the RangeScalar operation + // and right is going to be op1 or op2. In the case op1/op2 is QNaN or SNaN + // we want to preserve it instead. Otherwise we want to preserve the original + // result computed by RangeScalar. + // + // If both inputs are NaN, then we'll end up taking op1 by virtue of it being + // the latter fixup. + + if (ni == NI_System_Math_Max) + { + // QNAN: 0b0000: Preserve left + // SNAN: 0b0000 + // ZERO: 0b1000: +0 + // +ONE: 0b0000 + // -INF: 0b0000 + // +INF: 0b0000 + // -VAL: 0b0000 + // +VAL: 0b0000 + tbl->gtSimdVal.i32[0] = 0x0800; + } + else + { + assert(ni == NI_System_Math_Min); + + // QNAN: 0b0000: Preserve left + // SNAN: 0b0000 + // ZERO: 0b0111: -0 + // +ONE: 0b0000 + // -INF: 0b0000 + // +INF: 0b0000 + // -VAL: 0b0000 + // +VAL: 0b0000 + tbl->gtSimdVal.i32[0] = 0x0700; + } + + retNode = gtNewSimdHWIntrinsicNode(TYP_SIMD16, retNode, op2Clone, tbl, NI_AVX512F_FixupScalar, + callJitType, 16); + } + + retNode = gtNewSimdHWIntrinsicNode(callType, retNode, NI_Vector128_ToScalar, callJitType, 16); break; } #endif From 62ce778aef95bdf58a5130772d11ee5088b82226 Mon Sep 17 00:00:00 2001 From: Tanner Gooding Date: Thu, 15 Jun 2023 09:31:02 -0700 Subject: [PATCH 2/6] Ensure that Single/Double APIs can be directly handled as intrinsic --- src/coreclr/jit/compiler.h | 3 +- src/coreclr/jit/importercalls.cpp | 406 ++++++++++++------ .../src/System/Double.cs | 29 ++ .../System.Private.CoreLib/src/System/Math.cs | 1 + .../src/System/MathF.cs | 4 + .../src/System/Single.cs | 29 ++ 6 files changed, 345 insertions(+), 127 deletions(-) diff --git a/src/coreclr/jit/compiler.h b/src/coreclr/jit/compiler.h index f293dd622c8dcf..4c6138b1d817ce 100644 --- a/src/coreclr/jit/compiler.h +++ b/src/coreclr/jit/compiler.h @@ -3958,7 +3958,8 @@ class Compiler NamedIntrinsic intrinsicName, bool tailCall); NamedIntrinsic lookupNamedIntrinsic(CORINFO_METHOD_HANDLE method); - NamedIntrinsic lookupPrimitiveNamedIntrinsic(CORINFO_METHOD_HANDLE method, const char* methodName); + NamedIntrinsic lookupPrimitiveFloatNamedIntrinsic(CORINFO_METHOD_HANDLE method, const char* methodName); + NamedIntrinsic lookupPrimitiveIntNamedIntrinsic(CORINFO_METHOD_HANDLE method, const char* methodName); GenTree* impUnsupportedNamedIntrinsic(unsigned helper, CORINFO_METHOD_HANDLE method, CORINFO_SIG_INFO* sig, diff --git a/src/coreclr/jit/importercalls.cpp b/src/coreclr/jit/importercalls.cpp index 5f4df1618e793e..51c6b6336127eb 100644 --- a/src/coreclr/jit/importercalls.cpp +++ b/src/coreclr/jit/importercalls.cpp @@ -8289,6 +8289,15 @@ NamedIntrinsic Compiler::lookupNamedIntrinsic(CORINFO_METHOD_HANDLE method) break; } + case 'D': + { + if (strcmp(className, "Double") == 0) + { + result = lookupPrimitiveFloatNamedIntrinsic(method, methodName); + } + break; + } + case 'E': { if (strcmp(className, "Enum") == 0) @@ -8325,7 +8334,7 @@ NamedIntrinsic Compiler::lookupNamedIntrinsic(CORINFO_METHOD_HANDLE method) if ((strcmp(className, "Int32") == 0) || (strcmp(className, "Int64") == 0) || (strcmp(className, "IntPtr") == 0)) { - result = lookupPrimitiveNamedIntrinsic(method, methodName); + result = lookupPrimitiveIntNamedIntrinsic(method, methodName); } break; } @@ -8334,126 +8343,7 @@ NamedIntrinsic Compiler::lookupNamedIntrinsic(CORINFO_METHOD_HANDLE method) { if ((strcmp(className, "Math") == 0) || (strcmp(className, "MathF") == 0)) { - if (strcmp(methodName, "Abs") == 0) - { - result = NI_System_Math_Abs; - } - else if (strcmp(methodName, "Acos") == 0) - { - result = NI_System_Math_Acos; - } - else if (strcmp(methodName, "Acosh") == 0) - { - result = NI_System_Math_Acosh; - } - else if (strcmp(methodName, "Asin") == 0) - { - result = NI_System_Math_Asin; - } - else if (strcmp(methodName, "Asinh") == 0) - { - result = NI_System_Math_Asinh; - } - else if (strcmp(methodName, "Atan") == 0) - { - result = NI_System_Math_Atan; - } - else if (strcmp(methodName, "Atanh") == 0) - { - result = NI_System_Math_Atanh; - } - else if (strcmp(methodName, "Atan2") == 0) - { - result = NI_System_Math_Atan2; - } - else if (strcmp(methodName, "Cbrt") == 0) - { - result = NI_System_Math_Cbrt; - } - else if (strcmp(methodName, "Ceiling") == 0) - { - result = NI_System_Math_Ceiling; - } - else if (strcmp(methodName, "Cos") == 0) - { - result = NI_System_Math_Cos; - } - else if (strcmp(methodName, "Cosh") == 0) - { - result = NI_System_Math_Cosh; - } - else if (strcmp(methodName, "Exp") == 0) - { - result = NI_System_Math_Exp; - } - else if (strcmp(methodName, "Floor") == 0) - { - result = NI_System_Math_Floor; - } - else if (strcmp(methodName, "FMod") == 0) - { - result = NI_System_Math_FMod; - } - else if (strcmp(methodName, "FusedMultiplyAdd") == 0) - { - result = NI_System_Math_FusedMultiplyAdd; - } - else if (strcmp(methodName, "ILogB") == 0) - { - result = NI_System_Math_ILogB; - } - else if (strcmp(methodName, "Log") == 0) - { - result = NI_System_Math_Log; - } - else if (strcmp(methodName, "Log2") == 0) - { - result = NI_System_Math_Log2; - } - else if (strcmp(methodName, "Log10") == 0) - { - result = NI_System_Math_Log10; - } - else if (strcmp(methodName, "Max") == 0) - { - result = NI_System_Math_Max; - } - else if (strcmp(methodName, "Min") == 0) - { - result = NI_System_Math_Min; - } - else if (strcmp(methodName, "Pow") == 0) - { - result = NI_System_Math_Pow; - } - else if (strcmp(methodName, "Round") == 0) - { - result = NI_System_Math_Round; - } - else if (strcmp(methodName, "Sin") == 0) - { - result = NI_System_Math_Sin; - } - else if (strcmp(methodName, "Sinh") == 0) - { - result = NI_System_Math_Sinh; - } - else if (strcmp(methodName, "Sqrt") == 0) - { - result = NI_System_Math_Sqrt; - } - else if (strcmp(methodName, "Tan") == 0) - { - result = NI_System_Math_Tan; - } - else if (strcmp(methodName, "Tanh") == 0) - { - result = NI_System_Math_Tanh; - } - else if (strcmp(methodName, "Truncate") == 0) - { - result = NI_System_Math_Truncate; - } + result = lookupPrimitiveFloatNamedIntrinsic(method, methodName); } else if (strcmp(className, "MemoryExtensions") == 0) { @@ -8529,7 +8419,11 @@ NamedIntrinsic Compiler::lookupNamedIntrinsic(CORINFO_METHOD_HANDLE method) case 'S': { - if (strcmp(className, "Span`1") == 0) + if (strcmp(className, "Single") == 0) + { + result = lookupPrimitiveFloatNamedIntrinsic(method, methodName); + } + else if (strcmp(className, "Span`1") == 0) { if (strcmp(methodName, "get_Item") == 0) { @@ -8626,7 +8520,7 @@ NamedIntrinsic Compiler::lookupNamedIntrinsic(CORINFO_METHOD_HANDLE method) if ((strcmp(className, "UInt32") == 0) || (strcmp(className, "UInt64") == 0) || (strcmp(className, "UIntPtr") == 0)) { - result = lookupPrimitiveNamedIntrinsic(method, methodName); + result = lookupPrimitiveIntNamedIntrinsic(method, methodName); } break; } @@ -8673,7 +8567,7 @@ NamedIntrinsic Compiler::lookupNamedIntrinsic(CORINFO_METHOD_HANDLE method) { if (strcmp(className, "BitOperations") == 0) { - result = lookupPrimitiveNamedIntrinsic(method, methodName); + result = lookupPrimitiveIntNamedIntrinsic(method, methodName); } else { @@ -9034,7 +8928,267 @@ NamedIntrinsic Compiler::lookupNamedIntrinsic(CORINFO_METHOD_HANDLE method) } //------------------------------------------------------------------------ -// lookupPrimitiveNamedIntrinsic: map method to jit named intrinsic value +// lookupPrimitiveFloatNamedIntrinsic: map method to jit named intrinsic value +// +// Arguments: +// method -- method handle for method +// +// Return Value: +// Id for the named intrinsic, or Illegal if none. +// +// Notes: +// method should have CORINFO_FLG_INTRINSIC set in its attributes, +// otherwise it is not a named jit intrinsic. +// +NamedIntrinsic Compiler::lookupPrimitiveFloatNamedIntrinsic(CORINFO_METHOD_HANDLE method, const char* methodName) +{ + NamedIntrinsic result = NI_Illegal; + + switch (className[0]) + { + case 'A': + { + if (strcmp(methodName, "Abs") == 0) + { + result = NI_System_Math_Abs; + } + else if (strncmp(methodName, "Acos", 4) == 0) + { + methodName += 4; + + if (methodName[0] == '\0') + { + result = NI_System_Math_Acos; + } + else if (methodName[1] == '\0') + { + if (methodName[0] == 'h') + { + result = NI_System_Math_Acosh; + } + } + } + else if (strncmp(methodName, "Asin", 4) == 0) + { + methodName += 4; + + if (methodName[0] == '\0') + { + result = NI_System_Math_Asin; + } + else if (methodName[1] == '\0') + { + if (methodName[0] == 'h') + { + result = NI_System_Math_Asinh; + } + } + } + else if (strncmp(methodName, "Atan", 4) == 0) + { + methodName += 4; + + if (methodName[0] == '\0') + { + result = NI_System_Math_Atan; + } + else if (methodName[1] == '\0') + { + if (methodName[0] == 'h') + { + result = NI_System_Math_Atanh; + } + else if (methodName[0] == '2') + { + result = NI_System_Math_Atan2; + } + } + } + break; + } + + case 'C': + { + if (strcmp(methodName, "Cbrt") == 0) + { + result = NI_System_Math_Cbrt; + } + else if (strcmp(methodName, "Ceiling") == 0) + { + result = NI_System_Math_Ceiling; + } + else if (strncmp(methodName, "Cos", 3) == 0) + { + methodName += 3; + + if (methodName[0] == '\0') + { + result = NI_System_Math_Cos; + } + else if (methodName[1] == '\0') + { + if (methodName[0] == 'h') + { + result = NI_System_Math_Cosh; + } + } + } + break; + } + + case 'E': + { + if (strcmp(methodName, "Exp") == 0) + { + result = NI_System_Math_Exp; + } + break; + } + + case 'F': + { + if (strcmp(methodName, "Floor") == 0) + { + result = NI_System_Math_Floor; + } + else if (strcmp(methodName, "FMod") == 0) + { + result = NI_System_Math_FMod; + } + else if (strcmp(methodName, "FusedMultiplyAdd") == 0) + { + result = NI_System_Math_FusedMultiplyAdd; + } + break; + } + + case 'I': + { + if (strcmp(methodName, "ILogB") == 0) + { + result = NI_System_Math_ILogB; + } + break; + } + + case 'L': + { + if (strncmp(methodName, "Log", 3) == 0) + { + methodName += 3; + + if (methodName[0] == '\0') + { + result = NI_System_Math_Log; + } + else if (methodName[1] == '\0') + { + if (methodName[0] == '2') + { + result = NI_System_Math_Log2; + } + } + else if (strcmp(methodName, "10") == 0) + { + result = NI_System_Math_Log10; + } + } + break; + } + + case 'M': + { + if (strcmp(methodName, "Max") == 0) + { + result = NI_System_Math_Max; + } + else if (strcmp(methodName, "Min") == 0) + { + result = NI_System_Math_Min; + } + break; + } + + case 'P': + { + if (strcmp(methodName, "Pow") == 0) + { + result = NI_System_Math_Pow; + } + break; + } + + case 'R': + { + if (strcmp(methodName, "Round") == 0) + { + result = NI_System_Math_Round; + } + break; + } + + case 'S': + { + if (strncmp(methodName, "Sin", 3) == 0) + { + methodName += 3; + + if (methodName[0] == '\0') + { + result = NI_System_Math_Sin; + } + else if (methodName[1] == '\0') + { + if (methodName[0] == 'h') + { + result = NI_System_Math_Sinh; + } + } + } + else if (strcmp(methodName, "Sqrt") == 0) + { + result = NI_System_Math_Sqrt; + } + break; + } + + case 'T': + { + if (strncmp(methodName, "Tan", 3) == 0) + { + methodName += 3; + + if (methodName[0] == '\0') + { + result = NI_System_Math_Tan; + } + else if (methodName[1] == '\0') + { + if (methodName[0] == 'h') + { + result = NI_System_Math_Tanh; + } + } + result = NI_System_Math_Tan; + } + else if (strcmp(methodName, "Truncate") == 0) + { + result = NI_System_Math_Truncate; + } + break; + } + + default: + { + break; + } + } + + return result; +} + +//------------------------------------------------------------------------ +// lookupPrimitiveIntNamedIntrinsic: map method to jit named intrinsic value // // Arguments: // method -- method handle for method @@ -9046,7 +9200,7 @@ NamedIntrinsic Compiler::lookupNamedIntrinsic(CORINFO_METHOD_HANDLE method) // method should have CORINFO_FLG_INTRINSIC set in its attributes, // otherwise it is not a named jit intrinsic. // -NamedIntrinsic Compiler::lookupPrimitiveNamedIntrinsic(CORINFO_METHOD_HANDLE method, const char* methodName) +NamedIntrinsic Compiler::lookupPrimitiveIntNamedIntrinsic(CORINFO_METHOD_HANDLE method, const char* methodName) { NamedIntrinsic result = NI_Illegal; diff --git a/src/libraries/System.Private.CoreLib/src/System/Double.cs b/src/libraries/System.Private.CoreLib/src/System/Double.cs index aaa637ae02a6f3..e51971b3358bd5 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Double.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Double.cs @@ -571,6 +571,7 @@ public static bool IsPow2(double value) } /// + [Intrinsic] public static double Log2(double value) => Math.Log2(value); // @@ -624,6 +625,7 @@ public static bool IsPow2(double value) // /// + [Intrinsic] public static double Exp(double x) => Math.Exp(x); /// @@ -646,12 +648,15 @@ public static bool IsPow2(double value) // /// + [Intrinsic] public static double Ceiling(double x) => Math.Ceiling(x); /// + [Intrinsic] public static double Floor(double x) => Math.Floor(x); /// + [Intrinsic] public static double Round(double x) => Math.Round(x); /// @@ -664,6 +669,7 @@ public static bool IsPow2(double value) public static double Round(double x, int digits, MidpointRounding mode) => Math.Round(x, digits, mode); /// + [Intrinsic] public static double Truncate(double x) => Math.Truncate(x); /// @@ -819,6 +825,7 @@ bool IFloatingPoint.TryWriteSignificandLittleEndian(Span destinati static double IFloatingPointIeee754.PositiveInfinity => PositiveInfinity; /// + [Intrinsic] public static double Atan2(double y, double x) => Math.Atan2(y, x); /// @@ -831,12 +838,14 @@ bool IFloatingPoint.TryWriteSignificandLittleEndian(Span destinati public static double BitIncrement(double x) => Math.BitIncrement(x); /// + [Intrinsic] public static double FusedMultiplyAdd(double left, double right, double addend) => Math.FusedMultiplyAdd(left, right, addend); /// public static double Ieee754Remainder(double left, double right) => Math.IEEERemainder(left, right); /// + [Intrinsic] public static int ILogB(double x) => Math.ILogB(x); /// @@ -859,21 +868,27 @@ bool IFloatingPoint.TryWriteSignificandLittleEndian(Span destinati // /// + [Intrinsic] public static double Acosh(double x) => Math.Acosh(x); /// + [Intrinsic] public static double Asinh(double x) => Math.Asinh(x); /// + [Intrinsic] public static double Atanh(double x) => Math.Atanh(x); /// + [Intrinsic] public static double Cosh(double x) => Math.Cosh(x); /// + [Intrinsic] public static double Sinh(double x) => Math.Sinh(x); /// + [Intrinsic] public static double Tanh(double x) => Math.Tanh(x); // @@ -888,6 +903,7 @@ bool IFloatingPoint.TryWriteSignificandLittleEndian(Span destinati // /// + [Intrinsic] public static double Log(double x) => Math.Log(x); /// @@ -900,6 +916,7 @@ bool IFloatingPoint.TryWriteSignificandLittleEndian(Span destinati public static double Log2P1(double x) => Math.Log2(x + 1); /// + [Intrinsic] public static double Log10(double x) => Math.Log10(x); /// @@ -947,6 +964,7 @@ bool IFloatingPoint.TryWriteSignificandLittleEndian(Span destinati public static double CopySign(double value, double sign) => Math.CopySign(value, sign); /// + [Intrinsic] public static double Max(double x, double y) => Math.Max(x, y); /// @@ -972,6 +990,7 @@ public static double MaxNumber(double x, double y) } /// + [Intrinsic] public static double Min(double x, double y) => Math.Min(x, y); /// @@ -1013,6 +1032,7 @@ public static double MinNumber(double x, double y) static double INumberBase.Zero => Zero; /// + [Intrinsic] public static double Abs(double value) => Math.Abs(value); /// @@ -1429,6 +1449,7 @@ private static bool TryConvertTo(double value, [MaybeNullWhen(false)] ou // /// + [Intrinsic] public static double Pow(double x, double y) => Math.Pow(x, y); // @@ -1436,6 +1457,7 @@ private static bool TryConvertTo(double value, [MaybeNullWhen(false)] ou // /// + [Intrinsic] public static double Cbrt(double x) => Math.Cbrt(x); /// @@ -1719,6 +1741,7 @@ static double NegativeN(double x, int n) } /// + [Intrinsic] public static double Sqrt(double x) => Math.Sqrt(x); // @@ -1750,6 +1773,7 @@ static double NegativeN(double x, int n) // /// + [Intrinsic] public static double Acos(double x) => Math.Acos(x); /// @@ -1759,6 +1783,7 @@ public static double AcosPi(double x) } /// + [Intrinsic] public static double Asin(double x) => Math.Asin(x); /// @@ -1768,6 +1793,7 @@ public static double AsinPi(double x) } /// + [Intrinsic] public static double Atan(double x) => Math.Atan(x); /// @@ -1777,6 +1803,7 @@ public static double AtanPi(double x) } /// + [Intrinsic] public static double Cos(double x) => Math.Cos(x); /// @@ -1869,6 +1896,7 @@ public static double CosPi(double x) } /// + [Intrinsic] public static double Sin(double x) => Math.Sin(x); /// @@ -2076,6 +2104,7 @@ public static double SinPi(double x) } /// + [Intrinsic] public static double Tan(double x) => Math.Tan(x); /// diff --git a/src/libraries/System.Private.CoreLib/src/System/Math.cs b/src/libraries/System.Private.CoreLib/src/System/Math.cs index 5c7252bfefffe3..c1b4273eb272ec 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Math.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Math.cs @@ -812,6 +812,7 @@ public static double IEEERemainder(double x, double y) } } + [Intrinsic] public static int ILogB(double x) { // Implementation based on https://git.musl-libc.org/cgit/musl/tree/src/math/ilogb.c diff --git a/src/libraries/System.Private.CoreLib/src/System/MathF.cs b/src/libraries/System.Private.CoreLib/src/System/MathF.cs index 31ae24f93e92d1..6179add0dc8a7d 100644 --- a/src/libraries/System.Private.CoreLib/src/System/MathF.cs +++ b/src/libraries/System.Private.CoreLib/src/System/MathF.cs @@ -47,6 +47,7 @@ public static partial class MathF private const int ILogB_Zero = (-1 - 0x7fffffff); + [Intrinsic] [MethodImpl(MethodImplOptions.AggressiveInlining)] public static float Abs(float x) { @@ -184,6 +185,7 @@ public static float IEEERemainder(float x, float y) } } + [Intrinsic] public static int ILogB(float x) { // Implementation based on https://git.musl-libc.org/cgit/musl/tree/src/math/ilogbf.c @@ -241,6 +243,7 @@ public static float Log(float x, float y) return Log(x) / Log(y); } + [Intrinsic] [MethodImpl(MethodImplOptions.AggressiveInlining)] public static float Max(float x, float y) { @@ -271,6 +274,7 @@ public static float MaxMagnitude(float x, float y) return y; } + [Intrinsic] [MethodImpl(MethodImplOptions.AggressiveInlining)] public static float Min(float x, float y) { diff --git a/src/libraries/System.Private.CoreLib/src/System/Single.cs b/src/libraries/System.Private.CoreLib/src/System/Single.cs index 42d63de43279b7..79f8e96e2371a0 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Single.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Single.cs @@ -563,6 +563,7 @@ public static bool IsPow2(float value) } /// + [Intrinsic] public static float Log2(float value) => MathF.Log2(value); // @@ -616,6 +617,7 @@ public static bool IsPow2(float value) // /// + [Intrinsic] public static float Exp(float x) => MathF.Exp(x); /// @@ -638,12 +640,15 @@ public static bool IsPow2(float value) // /// + [Intrinsic] public static float Ceiling(float x) => MathF.Ceiling(x); /// + [Intrinsic] public static float Floor(float x) => MathF.Floor(x); /// + [Intrinsic] public static float Round(float x) => MathF.Round(x); /// @@ -656,6 +661,7 @@ public static bool IsPow2(float value) public static float Round(float x, int digits, MidpointRounding mode) => MathF.Round(x, digits, mode); /// + [Intrinsic] public static float Truncate(float x) => MathF.Truncate(x); /// @@ -799,6 +805,7 @@ bool IFloatingPoint.TryWriteSignificandLittleEndian(Span destinatio static float IFloatingPointIeee754.PositiveInfinity => PositiveInfinity; /// + [Intrinsic] public static float Atan2(float y, float x) => MathF.Atan2(y, x); /// @@ -811,12 +818,14 @@ bool IFloatingPoint.TryWriteSignificandLittleEndian(Span destinatio public static float BitIncrement(float x) => MathF.BitIncrement(x); /// + [Intrinsic] public static float FusedMultiplyAdd(float left, float right, float addend) => MathF.FusedMultiplyAdd(left, right, addend); /// public static float Ieee754Remainder(float left, float right) => MathF.IEEERemainder(left, right); /// + [Intrinsic] public static int ILogB(float x) => MathF.ILogB(x); /// @@ -839,21 +848,27 @@ bool IFloatingPoint.TryWriteSignificandLittleEndian(Span destinatio // /// + [Intrinsic] public static float Acosh(float x) => MathF.Acosh(x); /// + [Intrinsic] public static float Asinh(float x) => MathF.Asinh(x); /// + [Intrinsic] public static float Atanh(float x) => MathF.Atanh(x); /// + [Intrinsic] public static float Cosh(float x) => MathF.Cosh(x); /// + [Intrinsic] public static float Sinh(float x) => MathF.Sinh(x); /// + [Intrinsic] public static float Tanh(float x) => MathF.Tanh(x); // @@ -868,6 +883,7 @@ bool IFloatingPoint.TryWriteSignificandLittleEndian(Span destinatio // /// + [Intrinsic] public static float Log(float x) => MathF.Log(x); /// @@ -877,6 +893,7 @@ bool IFloatingPoint.TryWriteSignificandLittleEndian(Span destinatio public static float LogP1(float x) => MathF.Log(x + 1); /// + [Intrinsic] public static float Log10(float x) => MathF.Log10(x); /// @@ -927,6 +944,7 @@ bool IFloatingPoint.TryWriteSignificandLittleEndian(Span destinatio public static float CopySign(float value, float sign) => MathF.CopySign(value, sign); /// + [Intrinsic] public static float Max(float x, float y) => MathF.Max(x, y); /// @@ -952,6 +970,7 @@ public static float MaxNumber(float x, float y) } /// + [Intrinsic] public static float Min(float x, float y) => MathF.Min(x, y); /// @@ -993,6 +1012,7 @@ public static float MinNumber(float x, float y) static float INumberBase.Zero => Zero; /// + [Intrinsic] public static float Abs(float value) => MathF.Abs(value); /// @@ -1409,6 +1429,7 @@ private static bool TryConvertTo(float value, [MaybeNullWhen(false)] out // /// + [Intrinsic] public static float Pow(float x, float y) => MathF.Pow(x, y); // @@ -1416,6 +1437,7 @@ private static bool TryConvertTo(float value, [MaybeNullWhen(false)] out // /// + [Intrinsic] public static float Cbrt(float x) => MathF.Cbrt(x); /// @@ -1596,6 +1618,7 @@ static float NegativeN(float x, int n) } /// + [Intrinsic] public static float Sqrt(float x) => MathF.Sqrt(x); // @@ -1627,6 +1650,7 @@ static float NegativeN(float x, int n) // /// + [Intrinsic] public static float Acos(float x) => MathF.Acos(x); /// @@ -1636,6 +1660,7 @@ public static float AcosPi(float x) } /// + [Intrinsic] public static float Asin(float x) => MathF.Asin(x); /// @@ -1645,6 +1670,7 @@ public static float AsinPi(float x) } /// + [Intrinsic] public static float Atan(float x) => MathF.Atan(x); /// @@ -1654,6 +1680,7 @@ public static float AtanPi(float x) } /// + [Intrinsic] public static float Cos(float x) => MathF.Cos(x); /// @@ -1746,6 +1773,7 @@ public static float CosPi(float x) } /// + [Intrinsic] public static float Sin(float x) => MathF.Sin(x); /// @@ -1953,6 +1981,7 @@ public static float SinPi(float x) } /// + [Intrinsic] public static float Tan(float x) => MathF.Tan(x); /// From 9814a3d42dc3c105711918f0d2484be755e30cdb Mon Sep 17 00:00:00 2001 From: Tanner Gooding Date: Thu, 15 Jun 2023 12:50:43 -0700 Subject: [PATCH 3/6] Ensure that the various Min and Max APIs are accelerated where possible --- src/coreclr/jit/codegenarmarch.cpp | 20 + src/coreclr/jit/compiler.h | 8 + src/coreclr/jit/gentree.cpp | 32 + src/coreclr/jit/importercalls.cpp | 868 ++++++++++++------ src/coreclr/jit/lsraarm64.cpp | 6 + src/coreclr/jit/namedintrinsiclist.h | 6 + src/coreclr/jit/utils.cpp | 427 ++++++++- src/coreclr/jit/utils.h | 24 + src/coreclr/jit/valuenum.cpp | 72 ++ src/coreclr/jit/valuenumfuncs.h | 6 + .../src/System/Double.cs | 6 + .../System.Private.CoreLib/src/System/Math.cs | 2 + .../src/System/MathF.cs | 2 + .../src/System/Single.cs | 6 + 14 files changed, 1180 insertions(+), 305 deletions(-) diff --git a/src/coreclr/jit/codegenarmarch.cpp b/src/coreclr/jit/codegenarmarch.cpp index c0581ee7f7f162..22f170d00e0f59 100644 --- a/src/coreclr/jit/codegenarmarch.cpp +++ b/src/coreclr/jit/codegenarmarch.cpp @@ -690,16 +690,36 @@ void CodeGen::genIntrinsic(GenTreeIntrinsic* treeNode) break; case NI_System_Math_Max: + { genConsumeOperands(treeNode->AsOp()); GetEmitter()->emitIns_R_R_R(INS_fmax, emitActualTypeSize(treeNode), treeNode->GetRegNum(), srcNode->GetRegNum(), treeNode->gtGetOp2()->GetRegNum()); break; + } + + case NI_System_Math_MaxNumber: + { + genConsumeOperands(treeNode->AsOp()); + GetEmitter()->emitIns_R_R_R(INS_fmaxnm, emitActualTypeSize(treeNode), treeNode->GetRegNum(), + srcNode->GetRegNum(), treeNode->gtGetOp2()->GetRegNum()); + break; + } case NI_System_Math_Min: + { genConsumeOperands(treeNode->AsOp()); GetEmitter()->emitIns_R_R_R(INS_fmin, emitActualTypeSize(treeNode), treeNode->GetRegNum(), srcNode->GetRegNum(), treeNode->gtGetOp2()->GetRegNum()); break; + } + + case NI_System_Math_MinNumber: + { + genConsumeOperands(treeNode->AsOp()); + GetEmitter()->emitIns_R_R_R(INS_fminnm, emitActualTypeSize(treeNode), treeNode->GetRegNum(), + srcNode->GetRegNum(), treeNode->gtGetOp2()->GetRegNum()); + break; + } #endif // TARGET_ARM64 case NI_System_Math_Sqrt: diff --git a/src/coreclr/jit/compiler.h b/src/coreclr/jit/compiler.h index 4c6138b1d817ce..1a21b506644b29 100644 --- a/src/coreclr/jit/compiler.h +++ b/src/coreclr/jit/compiler.h @@ -3957,6 +3957,14 @@ class Compiler var_types callType, NamedIntrinsic intrinsicName, bool tailCall); + GenTree* impMinMaxIntrinsic(CORINFO_METHOD_HANDLE method, + CORINFO_SIG_INFO* sig, + CorInfoType callJitType, + NamedIntrinsic intrinsicName, + bool tailCall, + bool isMax, + bool isMagnitude, + bool isNumber); NamedIntrinsic lookupNamedIntrinsic(CORINFO_METHOD_HANDLE method); NamedIntrinsic lookupPrimitiveFloatNamedIntrinsic(CORINFO_METHOD_HANDLE method, const char* methodName); NamedIntrinsic lookupPrimitiveIntNamedIntrinsic(CORINFO_METHOD_HANDLE method, const char* methodName); diff --git a/src/coreclr/jit/gentree.cpp b/src/coreclr/jit/gentree.cpp index f6089645f8c416..d3c27adc5a8820 100644 --- a/src/coreclr/jit/gentree.cpp +++ b/src/coreclr/jit/gentree.cpp @@ -5473,7 +5473,13 @@ unsigned Compiler::gtSetEvalOrder(GenTree* tree) case NI_System_Math_Log2: case NI_System_Math_Log10: case NI_System_Math_Max: + case NI_System_Math_MaxMagnitude: + case NI_System_Math_MaxMagnitudeNumber: + case NI_System_Math_MaxNumber: case NI_System_Math_Min: + case NI_System_Math_MinMagnitude: + case NI_System_Math_MinMagnitudeNumber: + case NI_System_Math_MinNumber: case NI_System_Math_Pow: case NI_System_Math_Round: case NI_System_Math_Sin: @@ -5820,9 +5826,17 @@ unsigned Compiler::gtSetEvalOrder(GenTree* tree) break; case NI_System_Math_Max: + case NI_System_Math_MaxMagnitude: + case NI_System_Math_MaxMagnitudeNumber: + case NI_System_Math_MaxNumber: case NI_System_Math_Min: + case NI_System_Math_MinMagnitude: + case NI_System_Math_MinMagnitudeNumber: + case NI_System_Math_MinNumber: + { level++; break; + } default: assert(!"Unknown binary GT_INTRINSIC operator"); @@ -12231,9 +12245,27 @@ void Compiler::gtDispTree(GenTree* tree, case NI_System_Math_Max: printf(" max"); break; + case NI_System_Math_MaxMagnitude: + printf(" maxMagnitude"); + break; + case NI_System_Math_MaxMagnitudeNumber: + printf(" maxMagnitudeNumber"); + break; + case NI_System_Math_MaxNumber: + printf(" maxNumber"); + break; case NI_System_Math_Min: printf(" min"); break; + case NI_System_Math_MinMagnitude: + printf(" minMagnitude"); + break; + case NI_System_Math_MinMagnitudeNumber: + printf(" minMagnitudeNumber"); + break; + case NI_System_Math_MinNumber: + printf(" minNumber"); + break; case NI_System_Math_Pow: printf(" pow"); break; diff --git a/src/coreclr/jit/importercalls.cpp b/src/coreclr/jit/importercalls.cpp index 51c6b6336127eb..03684a70ce65c7 100644 --- a/src/coreclr/jit/importercalls.cpp +++ b/src/coreclr/jit/importercalls.cpp @@ -3399,306 +3399,85 @@ GenTree* Compiler::impIntrinsic(GenTree* newobjThis, break; } -#if defined(TARGET_ARM64) - // ARM64 has fmax/fmin which are IEEE754:2019 minimum/maximum compatible case NI_System_Math_Max: - case NI_System_Math_Min: -#endif - -#if defined(FEATURE_HW_INTRINSICS) && defined(TARGET_XARCH) - case NI_System_Math_Max: - case NI_System_Math_Min: { - assert(varTypeIsFloating(callType)); - assert(sig->numArgs == 2); - - GenTreeDblCon* cnsNode = nullptr; - GenTree* otherNode = nullptr; - - GenTree* op2 = impStackTop().val; - GenTree* op1 = impStackTop(1).val; - - if (op2->IsCnsFltOrDbl()) - { - cnsNode = op2->AsDblCon(); - otherNode = op1; - } - else if (op1->IsCnsFltOrDbl()) - { - cnsNode = op1->AsDblCon(); - otherNode = op2; - } - - if (cnsNode == nullptr) - { - if (!compOpportunisticallyDependsOn(InstructionSet_AVX512DQ)) - { - // no constant node and no AVX512DQ.VL support, nothing to do - break; - } - - // We are constructing a chain of intrinsics similar to: - // var op1 = Vector128.CreateScalarUnsafe(x); - // var op2 = Vector128.CreateScalarUnsafe(y); - // - // var tmp = Avx512DQ.RangeScalar(op1, op2, imm8); - // var tbl = Vector128.CreateScalarUnsafe(0x00); - // - // tmp = Avx512F.FixupScalar(tmp, op2, tbl, 0x00); - // tmp = Avx512F.FixupScalar(tmp, op1, tbl, 0x00); - // - // return tmp.ToScalar(); - - // RangeScalar operates by default almost as MaxNumber or MinNumber - // but, it propagates sNaN and does not propagate qNaN. So we need - // an additional fixup to ensure we propagate qNaN as well. - - uint8_t imm8; - - if (ni == NI_System_Math_Max) - { - // 0b01_00: Sign(CompareResult), MaxValue - imm8 = 0x04; - } - else - { - assert(ni == NI_System_Math_Min); - - // 0b01_01: Sign(CompareResult), MinValue - imm8 = 0x05; - } - - GenTree* op3 = gtNewIconNode(imm8); - GenTree* op2 = gtNewSimdCreateScalarUnsafeNode(TYP_SIMD16, impPopStack().val, callJitType, 16); - GenTree* op1 = gtNewSimdCreateScalarUnsafeNode(TYP_SIMD16, impPopStack().val, callJitType, 16); - - GenTree* op2Clone; - op2 = - impCloneExpr(op2, &op2Clone, CHECK_SPILL_ALL, nullptr DEBUGARG("Cloning op2 for Math.Max/Min")); - - GenTree* op1Clone; - op1 = - impCloneExpr(op1, &op1Clone, CHECK_SPILL_ALL, nullptr DEBUGARG("Cloning op1 for Math.Max/Min")); - - GenTree* tmp = - gtNewSimdHWIntrinsicNode(TYP_SIMD16, op1, op2, op3, NI_AVX512DQ_RangeScalar, callJitType, 16); - - GenTreeVecCon* tbl = gtNewVconNode(TYP_SIMD16); - - // FixupScalar(left, right, table, control) computes the input type of right - // adjusts it based on the table and then returns - // - // In our case, left is going to be the result of the RangeScalar operation - // and right is going to be op1 or op2. In the case op1/op2 is QNaN or SNaN - // we want to preserve it instead. Otherwise we want to preserve the original - // result computed by RangeScalar. - // - // If both inputs are NaN, then we'll end up taking op1 by virtue of it being - // the latter fixup. - // - // QNAN: 0b0001: Preserve right - // SNAN: 0b0001 - // ZERO: 0b0000: Preserve left - // +ONE: 0b0000 - // -INF: 0b0000 - // +INF: 0b0000 - // -VAL: 0b0000 - // +VAL: 0b0000 - tbl->gtSimdVal.i32[0] = 0x11; - - tmp = gtNewSimdHWIntrinsicNode(TYP_SIMD16, tmp, op2Clone, tbl, NI_AVX512F_FixupScalar, callJitType, - 16); - tmp = gtNewSimdHWIntrinsicNode(TYP_SIMD16, tmp, op1Clone, tbl, NI_AVX512F_FixupScalar, callJitType, - 16); - - retNode = gtNewSimdHWIntrinsicNode(callType, tmp, NI_Vector128_ToScalar, callJitType, 16); - break; - } - - if (otherNode->IsCnsFltOrDbl()) - { - // both are constant, we can fold this operation completely. Pop both peeked values - - if (ni == NI_System_Math_Max) - { - cnsNode->SetDconValue( - FloatingPointUtils::maximum(cnsNode->DconValue(), otherNode->AsDblCon()->DconValue())); - } - else - { - assert(ni == NI_System_Math_Min); - cnsNode->SetDconValue( - FloatingPointUtils::minimum(cnsNode->DconValue(), otherNode->AsDblCon()->DconValue())); - } - - retNode = cnsNode; - - impPopStack(); - impPopStack(); - DEBUG_DESTROY_NODE(otherNode); - - break; - } - - // only one is constant, we can fold in specialized scenarios - - if (cnsNode->IsFloatNaN()) - { - impSpillSideEffects(false, CHECK_SPILL_ALL DEBUGARG("spill side effects before propagating NaN")); - - // maxsd, maxss, minsd, and minss all return op2 if either is NaN - // we require NaN to be propagated so ensure the known NaN is op2 - - impPopStack(); - impPopStack(); - DEBUG_DESTROY_NODE(otherNode); - - retNode = cnsNode; - break; - } - - if (!compOpportunisticallyDependsOn(InstructionSet_SSE2)) - { - break; - } - - bool needsFixup = false; - - if (ni == NI_System_Math_Max) - { - // maxsd, maxss return op2 if both inputs are 0 of either sign - // we require +0 to be greater than -0, so we can't handle if - // the known constant is +0. This is because if the unknown value - // is -0, we'd need the cns to be op2. But if the unknown value - // is NaN, we'd need the cns to be op1 instead. - // - // However, if AVX512DQ is supported we have access to vfixupimmsd - // and vfixupimmss. This can be used to account for +0 vs -0. - - if (cnsNode->IsFloatPositiveZero()) - { - if (!compOpportunisticallyDependsOn(InstructionSet_AVX512F)) - { - break; - } - needsFixup = true; - } - - // Given the checks, op1 can safely be the cns and op2 the other node - - ni = (callType == TYP_DOUBLE) ? NI_SSE2_Max : NI_SSE_Max; - - // one is constant and we know its something we can handle, so pop both peeked values - - op1 = cnsNode; - op2 = otherNode; - } - else - { - assert(ni == NI_System_Math_Min); - - // minsd, minss return op2 if both inputs are 0 of either sign - // we require -0 to be lesser than +0, so we can't handle if - // the known constant is -0. This is because if the unknown value - // is +0, we'd need the cns to be op2. But if the unknown value - // is NaN, we'd need the cns to be op1 instead. - - if (cnsNode->IsFloatNegativeZero()) - { - if (!compOpportunisticallyDependsOn(InstructionSet_AVX512F)) - { - break; - } - needsFixup = true; - } - - // Given the checks, op1 can safely be the cns and op2 the other node - - ni = (callType == TYP_DOUBLE) ? NI_SSE2_Min : NI_SSE_Min; - - // one is constant and we know its something we can handle, so pop both peeked values + bool isMax = true; + bool isMagnitude = false; + bool isNumber = false; - op1 = cnsNode; - op2 = otherNode; - } + retNode = impMinMaxIntrinsic(method, sig, callJitType, ni, tailCall, isMax, isMagnitude, isNumber); + break; + } - assert(op1->IsCnsFltOrDbl() && !op2->IsCnsFltOrDbl()); + case NI_System_Math_Min: + { + bool isMax = false; + bool isMagnitude = false; + bool isNumber = false; - impPopStack(); - impPopStack(); + retNode = impMinMaxIntrinsic(method, sig, callJitType, ni, tailCall, isMax, isMagnitude, isNumber); + break; + } - GenTreeVecCon* vecCon = gtNewVconNode(TYP_SIMD16); + case NI_System_Math_MaxMagnitude: + { + bool isMax = true; + bool isMagnitude = true; + bool isNumber = false; - if (callJitType == CORINFO_TYPE_FLOAT) - { - vecCon->gtSimdVal.f32[0] = (float)op1->AsDblCon()->DconValue(); - } - else - { - vecCon->gtSimdVal.f64[0] = op1->AsDblCon()->DconValue(); - } + retNode = impMinMaxIntrinsic(method, sig, callJitType, ni, tailCall, isMax, isMagnitude, isNumber); + break; + } - op1 = vecCon; - op2 = gtNewSimdCreateScalarUnsafeNode(TYP_SIMD16, op2, callJitType, 16); + case NI_System_Math_MinMagnitude: + { + bool isMax = false; + bool isMagnitude = true; + bool isNumber = false; - retNode = gtNewSimdHWIntrinsicNode(TYP_SIMD16, op1, op2, ni, callJitType, 16); + retNode = impMinMaxIntrinsic(method, sig, callJitType, ni, tailCall, isMax, isMagnitude, isNumber); + break; + } - if (needsFixup) - { - GenTree* op2Clone; - op2 = - impCloneExpr(op2, &op2Clone, CHECK_SPILL_ALL, nullptr DEBUGARG("Cloning op2 for Math.Max/Min")); + case NI_System_Math_MaxMagnitudeNumber: + { + bool isMax = true; + bool isMagnitude = true; + bool isNumber = true; - retNode->AsHWIntrinsic()->Op(2) = op2; + retNode = impMinMaxIntrinsic(method, sig, callJitType, ni, tailCall, isMax, isMagnitude, isNumber); + break; + } - GenTreeVecCon* tbl = gtNewVconNode(TYP_SIMD16); + case NI_System_Math_MinMagnitudeNumber: + { + bool isMax = false; + bool isMagnitude = true; + bool isNumber = true; - // FixupScalar(left, right, table, control) computes the input type of right - // adjusts it based on the table and then returns - // - // In our case, left is going to be the result of the RangeScalar operation - // and right is going to be op1 or op2. In the case op1/op2 is QNaN or SNaN - // we want to preserve it instead. Otherwise we want to preserve the original - // result computed by RangeScalar. - // - // If both inputs are NaN, then we'll end up taking op1 by virtue of it being - // the latter fixup. + retNode = impMinMaxIntrinsic(method, sig, callJitType, ni, tailCall, isMax, isMagnitude, isNumber); + break; + } - if (ni == NI_System_Math_Max) - { - // QNAN: 0b0000: Preserve left - // SNAN: 0b0000 - // ZERO: 0b1000: +0 - // +ONE: 0b0000 - // -INF: 0b0000 - // +INF: 0b0000 - // -VAL: 0b0000 - // +VAL: 0b0000 - tbl->gtSimdVal.i32[0] = 0x0800; - } - else - { - assert(ni == NI_System_Math_Min); + case NI_System_Math_MaxNumber: + { + bool isMax = true; + bool isMagnitude = false; + bool isNumber = true; - // QNAN: 0b0000: Preserve left - // SNAN: 0b0000 - // ZERO: 0b0111: -0 - // +ONE: 0b0000 - // -INF: 0b0000 - // +INF: 0b0000 - // -VAL: 0b0000 - // +VAL: 0b0000 - tbl->gtSimdVal.i32[0] = 0x0700; - } + retNode = impMinMaxIntrinsic(method, sig, callJitType, ni, tailCall, isMax, isMagnitude, isNumber); + break; + } - retNode = gtNewSimdHWIntrinsicNode(TYP_SIMD16, retNode, op2Clone, tbl, NI_AVX512F_FixupScalar, - callJitType, 16); - } + case NI_System_Math_MinNumber: + { + bool isMax = false; + bool isMagnitude = false; + bool isNumber = true; - retNode = gtNewSimdHWIntrinsicNode(callType, retNode, NI_Vector128_ToScalar, callJitType, 16); + retNode = impMinMaxIntrinsic(method, sig, callJitType, ni, tailCall, isMax, isMagnitude, isNumber); break; } -#endif case NI_System_Math_Pow: case NI_System_Math_Round: @@ -6859,7 +6638,13 @@ bool Compiler::IsMathIntrinsic(NamedIntrinsic intrinsicName) case NI_System_Math_Log2: case NI_System_Math_Log10: case NI_System_Math_Max: + case NI_System_Math_MaxMagnitude: + case NI_System_Math_MaxMagnitudeNumber: + case NI_System_Math_MaxNumber: case NI_System_Math_Min: + case NI_System_Math_MinMagnitude: + case NI_System_Math_MinMagnitudeNumber: + case NI_System_Math_MinNumber: case NI_System_Math_Pow: case NI_System_Math_Round: case NI_System_Math_Sin: @@ -8132,6 +7917,473 @@ GenTree* Compiler::impMathIntrinsic(CORINFO_METHOD_HANDLE method, return op1; } +//------------------------------------------------------------------------ +// impMinMaxIntrinsic: Imports a min or max intrinsic +// +// Arguments: +// method - The handle of the method being imported +// callType - The underlying type for the call +// intrinsicName - The intrinsic being imported +// tailCall - true if the method is a tail call; otherwise false +// isMax - true if the intrinsic computes the max; false for the min +// isMagnitude - true if the intrinsic compares using the absolute value of the inputs +// isNumber - true if the intrinsic propagates the number; false for NaN +// +GenTree* Compiler::impMinMaxIntrinsic(CORINFO_METHOD_HANDLE method, + CORINFO_SIG_INFO* sig, + CorInfoType callJitType, + NamedIntrinsic intrinsicName, + bool tailCall, + bool isMax, + bool isMagnitude, + bool isNumber) +{ + var_types callType = JITtype2varType(callJitType); + + assert(varTypeIsFloating(callType)); + assert(sig->numArgs == 2); + + GenTreeDblCon* cnsNode = nullptr; + GenTree* otherNode = nullptr; + + GenTree* op2 = impStackTop().val; + GenTree* op1 = impStackTop(1).val; + + if (op2->IsCnsFltOrDbl()) + { + cnsNode = op2->AsDblCon(); + otherNode = op1; + } + else if (op1->IsCnsFltOrDbl()) + { + cnsNode = op1->AsDblCon(); + otherNode = op2; + } + + if (cnsNode != nullptr) + { + if (otherNode->IsCnsFltOrDbl()) + { + // both are constant, we can fold this operation completely. Pop both peeked values + + double x = cnsNode->DconValue(); + double y = otherNode->AsDblCon()->DconValue(); + double z; + + if (isMax) + { + if (isMagnitude) + { + if (isNumber) + { + z = FloatingPointUtils::maximumMagnitudeNumber(x, y); + } + else + { + z = FloatingPointUtils::maximumMagnitude(x, y); + } + } + else if (isNumber) + { + z = FloatingPointUtils::maximumNumber(x, y); + } + else + { + z = FloatingPointUtils::maximum(x, y); + } + } + else + { + if (isMagnitude) + { + if (isNumber) + { + z = FloatingPointUtils::minimumMagnitudeNumber(x, y); + } + else + { + z = FloatingPointUtils::minimumMagnitude(x, y); + } + } + else if (isNumber) + { + z = FloatingPointUtils::minimumNumber(x, y); + } + else + { + z = FloatingPointUtils::minimum(x, y); + } + } + cnsNode->SetDconValue(z); + + impPopStack(); + impPopStack(); + + DEBUG_DESTROY_NODE(otherNode); + return cnsNode; + } + + // only one is constant, we can fold in specialized scenarios + + if (cnsNode->IsFloatNaN()) + { + impSpillSideEffects(false, CHECK_SPILL_ALL DEBUGARG("spill side effects before propagating NaN")); + + impPopStack(); + impPopStack(); + + if (isNumber) + { + DEBUG_DESTROY_NODE(cnsNode); + return otherNode; + } + else + { + DEBUG_DESTROY_NODE(otherNode); + return cnsNode; + } + } + +#if defined(FEATURE_HW_INTRINSICS) && defined(TARGET_XARCH) + if (!isMagnitude && compOpportunisticallyDependsOn(InstructionSet_SSE2)) + { + bool needsFixup = false; + bool canHandle = false; + + if (isMax) + { + // maxsd, maxss return op2 if both inputs are 0 of either sign + // we require +0 to be greater than -0 we also require NaN to + // not be propagated for isNumber and to be propagated otherwise. + // + // This means for isNumber we want to do `max other, cns` and + // can only handle cns being -0 if Avx512F is supported. This is + // because if other was NaN, we want to return the non-NaN cns. + // But if cns was -0 and other was +0 we'd want to return +0 and + // so need to be able to fixup the result. + // + // For !isNumber we have the inverse and want `max cns, other` and + // can only handle cns being +0 if Avx512F is supported. This is + // because if other was NaN, we want to return other and if cns + // was +0 and other was -0 we'd want to return +0 and so need + // so need to be able to fixup the result. + + if (isNumber) + { + needsFixup = cnsNode->IsFloatNegativeZero(); + } + else + { + needsFixup = cnsNode->IsFloatPositiveZero(); + } + + if (!needsFixup || compOpportunisticallyDependsOn(InstructionSet_AVX512F)) + { + // Given the checks, op1 can safely be the cns and op2 the other node + + intrinsicName = (callType == TYP_DOUBLE) ? NI_SSE2_Max : NI_SSE_Max; + + // one is constant and we know its something we can handle, so pop both peeked values + + op1 = cnsNode; + op2 = otherNode; + + canHandle = true; + } + } + else + { + // minsd, minss return op2 if both inputs are 0 of either sign + // we require -0 to be lesser than +0, we also require NaN to + // not be propagated for isNumber and to be propagated otherwise. + // + // This means for isNumber we want to do `min other, cns` and + // can only handle cns being +0 if Avx512F is supported. This is + // because if other was NaN, we want to return the non-NaN cns. + // But if cns was +0 and other was -0 we'd want to return -0 and + // so need to be able to fixup the result. + // + // For !isNumber we have the inverse and want `min cns, other` and + // can only handle cns being -0 if Avx512F is supported. This is + // because if other was NaN, we want to return other and if cns + // was -0 and other was +0 we'd want to return -0 and so need + // so need to be able to fixup the result. + + if (isNumber) + { + needsFixup = cnsNode->IsFloatNegativeZero(); + } + else + { + needsFixup = cnsNode->IsFloatPositiveZero(); + } + + if (!needsFixup || compOpportunisticallyDependsOn(InstructionSet_AVX512F)) + { + // Given the checks, op1 can safely be the cns and op2 the other node + + intrinsicName = (callType == TYP_DOUBLE) ? NI_SSE2_Min : NI_SSE_Min; + + // one is constant and we know its something we can handle, so pop both peeked values + + op1 = cnsNode; + op2 = otherNode; + + canHandle = true; + } + } + + if (canHandle) + { + assert(op1->IsCnsFltOrDbl() && !op2->IsCnsFltOrDbl()); + + impPopStack(); + impPopStack(); + + GenTreeVecCon* vecCon = gtNewVconNode(TYP_SIMD16); + + if (callJitType == CORINFO_TYPE_FLOAT) + { + vecCon->gtSimdVal.f32[0] = static_cast(op1->AsDblCon()->DconValue()); + } + else + { + vecCon->gtSimdVal.f64[0] = op1->AsDblCon()->DconValue(); + } + + op1 = vecCon; + op2 = gtNewSimdCreateScalarUnsafeNode(TYP_SIMD16, op2, callJitType, 16); + + GenTree* retNode = gtNewSimdHWIntrinsicNode(TYP_SIMD16, op1, op2, intrinsicName, callJitType, 16); + + if (needsFixup) + { + GenTree* op2Clone; + op2 = impCloneExpr(op2, &op2Clone, CHECK_SPILL_ALL, + nullptr DEBUGARG("Cloning non-constant for Math.Max/Min")); + + retNode->AsHWIntrinsic()->Op(2) = op2; + + GenTreeVecCon* tbl = gtNewVconNode(TYP_SIMD16); + + // FixupScalar(left, right, table, control) computes the input type of right + // adjusts it based on the table and then returns + // + // In our case, left is going to be the result of the RangeScalar operation + // and right is going to be op1 or op2. In the case op1/op2 is QNaN or SNaN + // we want to preserve it instead. Otherwise we want to preserve the original + // result computed by RangeScalar. + // + // If both inputs are NaN, then we'll end up taking op1 by virtue of it being + // the latter fixup. + + if (isMax) + { + // QNAN: 0b0000: Preserve left + // SNAN: 0b0000 + // ZERO: 0b1000: +0 + // +ONE: 0b0000 + // -INF: 0b0000 + // +INF: 0b0000 + // -VAL: 0b0000 + // +VAL: 0b0000 + tbl->gtSimdVal.i32[0] = 0x0800; + } + else + { + // QNAN: 0b0000: Preserve left + // SNAN: 0b0000 + // ZERO: 0b0111: -0 + // +ONE: 0b0000 + // -INF: 0b0000 + // +INF: 0b0000 + // -VAL: 0b0000 + // +VAL: 0b0000 + tbl->gtSimdVal.i32[0] = 0x0700; + } + + retNode = gtNewSimdHWIntrinsicNode(TYP_SIMD16, retNode, op2Clone, tbl, NI_AVX512F_FixupScalar, + callJitType, 16); + } + + if (isNumber) + { + std::swap(op1, op2); + + retNode->AsHWIntrinsic()->Op(1) = op2; + retNode->AsHWIntrinsic()->Op(2) = op1; + } + + return gtNewSimdHWIntrinsicNode(callType, retNode, NI_Vector128_ToScalar, callJitType, 16); + } + } +#endif // FEATURE_HW_INTRINSICS && TARGET_XARCH + } + +#if defined(FEATURE_HW_INTRINSICS) && defined(TARGET_XARCH) + if (compOpportunisticallyDependsOn(InstructionSet_AVX512DQ)) + { + // We are constructing a chain of intrinsics similar to: + // var op1 = Vector128.CreateScalarUnsafe(x); + // var op2 = Vector128.CreateScalarUnsafe(y); + // + // var tmp = Avx512DQ.RangeScalar(op1, op2, imm8); + // var tbl = Vector128.CreateScalarUnsafe(0x00); + // + // tmp = Avx512F.FixupScalar(tmp, op2, tbl, 0x00); + // tmp = Avx512F.FixupScalar(tmp, op1, tbl, 0x00); + // + // return tmp.ToScalar(); + + // RangeScalar operates by default almost as MaxNumber or MinNumber + // but, it propagates sNaN and does not propagate qNaN. So we need + // an additional fixup to ensure we propagate qNaN as well. + + uint8_t imm8; + + if (isMax) + { + if (isMagnitude) + { + // 0b01_11: Sign(CompareResult), Max-Abs Value + imm8 = 0x07; + } + else + { + // 0b01_01: Sign(CompareResult), Max Value + imm8 = 0x05; + } + } + else if (isMagnitude) + { + // 0b01_10: Sign(CompareResult), Min-Abs Value + imm8 = 0x06; + } + else + { + // 0b01_00: Sign(CompareResult), Min Value + imm8 = 0x04; + } + + GenTree* op3 = gtNewIconNode(imm8); + GenTree* op2 = gtNewSimdCreateScalarUnsafeNode(TYP_SIMD16, impPopStack().val, callJitType, 16); + GenTree* op1 = gtNewSimdCreateScalarUnsafeNode(TYP_SIMD16, impPopStack().val, callJitType, 16); + + GenTree* op2Clone; + op2 = impCloneExpr(op2, &op2Clone, CHECK_SPILL_ALL, nullptr DEBUGARG("Cloning op2 for Math.Max/Min")); + + GenTree* op1Clone; + op1 = impCloneExpr(op1, &op1Clone, CHECK_SPILL_ALL, nullptr DEBUGARG("Cloning op1 for Math.Max/Min")); + + GenTree* tmp = gtNewSimdHWIntrinsicNode(TYP_SIMD16, op1, op2, op3, NI_AVX512DQ_RangeScalar, callJitType, 16); + + // FixupScalar(left, right, table, control) computes the input type of right + // adjusts it based on the table and then returns + // + // In our case, left is going to be the result of the RangeScalar operation, + // which is either sNaN or a normal value, and right is going to be op1 or op2. + + GenTree* tbl1 = gtNewVconNode(TYP_SIMD16); + GenTree* tbl2; + + // We currently have (commutative) + // * snan, snan = snan + // * snan, qnan = snan + // * snan, norm = snan + // * qnan, qnan = qnan + // * qnan, norm = norm + // * norm, norm = norm + + if (isNumber) + { + // We need to fixup the case of: + // * snan, norm = snan + // + // Instead, it should be: + // * snan, norm = norm + + // First look at op1 and op2 using op2 as the classification + // + // If op2 is norm, we take op2 (norm) + // If op2 is nan, we take op1 ( nan or norm) + // + // Thus, if one input was norm the fixup is now norm + + // QNAN: 0b0000: Preserve left + // SNAN: 0b0000 + // ZERO: 0b0001: Preserve right + // +ONE: 0b0001 + // -INF: 0b0001 + // +INF: 0b0001 + // -VAL: 0b0001 + // +VAL: 0b0001 + tbl1->AsVecCon()->gtSimdVal.i32[0] = 0x11111100; + + // Next look at result and fixup using result as the classification + // + // If result is norm, we take the result (norm) + // If result is nan, we take the fixup ( nan or norm) + // + // Thus if either input was snan, we now have norm as expected + // Otherwise, the result was already correct + + tbl1 = impCloneExpr(tbl1, &tbl2, CHECK_SPILL_ALL, nullptr DEBUGARG("Cloning tbl for Math.Max/Min")); + + op1Clone = + gtNewSimdHWIntrinsicNode(TYP_SIMD16, op1Clone, op2Clone, tbl1, NI_AVX512F_FixupScalar, callJitType, 16); + + tmp = gtNewSimdHWIntrinsicNode(TYP_SIMD16, op1Clone, tmp, tbl2, NI_AVX512F_FixupScalar, callJitType, 16); + } + else + { + // We need to fixup the case of: + // * qnan, norm = norm + // + // Instead, it should be: + // * qnan, norm = qnan + + // First look at op1 and op2 using op2 as the classification + // + // If op2 is norm, we take op1 ( nan or norm) + // If op2 is snan, we take op1 ( nan or norm) + // If op2 is qnan, we take op2 (qnan) + // + // Thus, if either input was qnan the fixup is now qnan + + // QNAN: 0b0001: Preserve right + // SNAN: 0b0000: Preserve left + // ZERO: 0b0000 + // +ONE: 0b0000 + // -INF: 0b0000 + // +INF: 0b0000 + // -VAL: 0b0000 + // +VAL: 0b0000 + tbl1->AsVecCon()->gtSimdVal.i32[0] = 0x00000001; + + // Next look at result and fixup using fixup as the classification + // + // If fixup is norm, we take the result (norm) + // If fixup is sNaN, we take the result (sNaN) + // If fixup is qNaN, we take the fixup (qNaN) + // + // Thus if the fixup was qnan, we now have qnan as expected + // Otherwise, the result was already correct + + tbl1 = impCloneExpr(tbl1, &tbl2, CHECK_SPILL_ALL, nullptr DEBUGARG("Cloning tbl for Math.Max/Min")); + + op1Clone = + gtNewSimdHWIntrinsicNode(TYP_SIMD16, op1Clone, op2Clone, tbl1, NI_AVX512F_FixupScalar, callJitType, 16); + + tmp = gtNewSimdHWIntrinsicNode(TYP_SIMD16, tmp, op1Clone, tbl2, NI_AVX512F_FixupScalar, callJitType, 16); + } + + return gtNewSimdHWIntrinsicNode(callType, tmp, NI_Vector128_ToScalar, callJitType, 16); + } +#endif // FEATURE_HW_INTRINSICS && TARGET_XARCH + + return impMathIntrinsic(method, sig, callType, intrinsicName, tailCall); +} + //------------------------------------------------------------------------ // lookupNamedIntrinsic: map method to jit named intrinsic value // @@ -8944,7 +9196,7 @@ NamedIntrinsic Compiler::lookupPrimitiveFloatNamedIntrinsic(CORINFO_METHOD_HANDL { NamedIntrinsic result = NI_Illegal; - switch (className[0]) + switch (methodName[0]) { case 'A': { @@ -9098,13 +9350,57 @@ NamedIntrinsic Compiler::lookupPrimitiveFloatNamedIntrinsic(CORINFO_METHOD_HANDL case 'M': { - if (strcmp(methodName, "Max") == 0) + if (strncmp(methodName, "Max", 3) == 0) { - result = NI_System_Math_Max; + methodName += 3; + + if (methodName[0] == '\0') + { + result = NI_System_Math_Max; + } + else if (strncmp(methodName, "Magnitude", 9) == 0) + { + methodName += 9; + + if (methodName[0] == '\0') + { + result = NI_System_Math_MaxMagnitude; + } + else if (strcmp(methodName, "Number") == 0) + { + result = NI_System_Math_MaxMagnitudeNumber; + } + } + else if (strcmp(methodName, "Number") == 0) + { + result = NI_System_Math_MaxNumber; + } } - else if (strcmp(methodName, "Min") == 0) + else if (strncmp(methodName, "Min", 3) == 0) { - result = NI_System_Math_Min; + methodName += 3; + + if (methodName[0] == '\0') + { + result = NI_System_Math_Min; + } + else if (strncmp(methodName, "Magnitude", 9) == 0) + { + methodName += 9; + + if (methodName[0] == '\0') + { + result = NI_System_Math_MinMagnitude; + } + else if (strcmp(methodName, "Number") == 0) + { + result = NI_System_Math_MinMagnitudeNumber; + } + } + else if (strcmp(methodName, "Number") == 0) + { + result = NI_System_Math_MinNumber; + } } break; } diff --git a/src/coreclr/jit/lsraarm64.cpp b/src/coreclr/jit/lsraarm64.cpp index 23be48377907c3..1d22c1972c0aa6 100644 --- a/src/coreclr/jit/lsraarm64.cpp +++ b/src/coreclr/jit/lsraarm64.cpp @@ -873,6 +873,9 @@ int LinearScan::BuildNode(GenTree* tree) { case NI_System_Math_Max: case NI_System_Math_Min: + case NI_System_Math_MaxNumber: + case NI_System_Math_MinNumber: + { assert(varTypeIsFloating(tree->gtGetOp1())); assert(varTypeIsFloating(tree->gtGetOp2())); assert(tree->gtGetOp1()->TypeIs(tree->TypeGet())); @@ -881,6 +884,7 @@ int LinearScan::BuildNode(GenTree* tree) assert(dstCount == 1); BuildDef(tree); break; + } case NI_System_Math_Abs: case NI_System_Math_Ceiling: @@ -888,6 +892,7 @@ int LinearScan::BuildNode(GenTree* tree) case NI_System_Math_Truncate: case NI_System_Math_Round: case NI_System_Math_Sqrt: + { assert(varTypeIsFloating(tree->gtGetOp1())); assert(tree->gtGetOp1()->TypeIs(tree->TypeGet())); @@ -896,6 +901,7 @@ int LinearScan::BuildNode(GenTree* tree) assert(dstCount == 1); BuildDef(tree); break; + } default: unreached(); diff --git a/src/coreclr/jit/namedintrinsiclist.h b/src/coreclr/jit/namedintrinsiclist.h index 6bc31d315b5c36..1fcfc0930d762e 100644 --- a/src/coreclr/jit/namedintrinsiclist.h +++ b/src/coreclr/jit/namedintrinsiclist.h @@ -44,7 +44,13 @@ enum NamedIntrinsic : unsigned short NI_System_Math_Log2, NI_System_Math_Log10, NI_System_Math_Max, + NI_System_Math_MaxMagnitude, + NI_System_Math_MaxMagnitudeNumber, + NI_System_Math_MaxNumber, NI_System_Math_Min, + NI_System_Math_MinMagnitude, + NI_System_Math_MinMagnitudeNumber, + NI_System_Math_MinNumber, NI_System_Math_Pow, NI_System_Math_Round, NI_System_Math_Sin, diff --git a/src/coreclr/jit/utils.cpp b/src/coreclr/jit/utils.cpp index 27e5f14ac3fc00..f9443481255549 100644 --- a/src/coreclr/jit/utils.cpp +++ b/src/coreclr/jit/utils.cpp @@ -2423,9 +2423,10 @@ bool FloatingPointUtils::isPositiveZero(double val) //------------------------------------------------------------------------ // maximum: This matches the IEEE 754:2019 `maximum` function -// It propagates NaN inputs back to the caller and -// otherwise returns the larger of the inputs. It -// treats +0 as larger than -0 as per the specification. +// +// It propagates NaN inputs back to the caller and +// otherwise returns the greater of the inputs. It +// treats +0 as greater than -0 as per the specification. // // Arguments: // val1 - left operand @@ -2434,7 +2435,6 @@ bool FloatingPointUtils::isPositiveZero(double val) // Return Value: // Either val1 or val2 // - double FloatingPointUtils::maximum(double val1, double val2) { if (val1 != val2) @@ -2443,16 +2443,112 @@ double FloatingPointUtils::maximum(double val1, double val2) { return val2 < val1 ? val1 : val2; } + return val1; } + return isNegative(val2) ? val1 : val2; } +//------------------------------------------------------------------------ +// maximumMagnitude: This matches the IEEE 754:2019 `maximumMagnitude` function +// +// It propagates NaN inputs back to the caller and +// otherwise returns the input with a greater magnitude. +// It treats +0 as greater than -0 as per the specification. +// +// Arguments: +// x - left operand +// y - right operand +// +// Return Value: +// Either x or y +// +double FloatingPointUtils::maximumMagnitude(double x, double y) +{ + double ax = fabs(x); + double ay = fabs(y); + + if ((ax > ay) || isNaN(ax)) + { + return x; + } + + if (ax == ay) + { + return isNegative(x) ? y : x; + } + + return y; +} + +//------------------------------------------------------------------------ +// maximumMagnitudeNumber: // This matches the IEEE 754:2019 `maximumMagnitudeNumber` function +// +// It does not propagate NaN inputs back to the caller and +// otherwise returns the input with a larger magnitude. +// It treats +0 as larger than -0 as per the specification. +// +// Arguments: +// x - left operand +// y - right operand +// +// Return Value: +// Either x or y +// +double FloatingPointUtils::maximumMagnitudeNumber(double x, double y) +{ + double ax = fabs(x); + double ay = fabs(y); + + if ((ax > ay) || isNaN(ay)) + { + return x; + } + + if (ax == ay) + { + return isNegative(x) ? y : x; + } + + return y; +} + +//------------------------------------------------------------------------ +// maximumNumber: This matches the IEEE 754:2019 `maximumNumber` function +// +// It does not propagate NaN inputs back to the caller and +// otherwise returns the larger of the inputs. It +// treats +0 as larger than -0 as per the specification. +// +// Arguments: +// x - left operand +// y - right operand +// +// Return Value: +// Either x or y +// +double FloatingPointUtils::maximumNumber(double x, double y) +{ + if (x != y) + { + if (!isNaN(y)) + { + return y < x ? x : y; + } + + return x; + } + + return isNegative(y) ? x : y; +} + //------------------------------------------------------------------------ // maximum: This matches the IEEE 754:2019 `maximum` function -// It propagates NaN inputs back to the caller and -// otherwise returns the larger of the inputs. It -// treats +0 as larger than -0 as per the specification. +// +// It propagates NaN inputs back to the caller and +// otherwise returns the greater of the inputs. It +// treats +0 as greater than -0 as per the specification. // // Arguments: // val1 - left operand @@ -2461,7 +2557,6 @@ double FloatingPointUtils::maximum(double val1, double val2) // Return Value: // Either val1 or val2 // - float FloatingPointUtils::maximum(float val1, float val2) { if (val1 != val2) @@ -2470,16 +2565,112 @@ float FloatingPointUtils::maximum(float val1, float val2) { return val2 < val1 ? val1 : val2; } + return val1; } + return isNegative(val2) ? val1 : val2; } +//------------------------------------------------------------------------ +// maximumMagnitude: This matches the IEEE 754:2019 `maximumMagnitude` function +// +// It propagates NaN inputs back to the caller and +// otherwise returns the input with a greater magnitude. +// It treats +0 as greater than -0 as per the specification. +// +// Arguments: +// x - left operand +// y - right operand +// +// Return Value: +// Either x or y +// +float FloatingPointUtils::maximumMagnitude(float x, float y) +{ + float ax = fabsf(x); + float ay = fabsf(y); + + if ((ax > ay) || isNaN(ax)) + { + return x; + } + + if (ax == ay) + { + return isNegative(x) ? y : x; + } + + return y; +} + +//------------------------------------------------------------------------ +// maximumMagnitudeNumber: This matches the IEEE 754:2019 `maximumMagnitudeNumber` function +// +// It does not propagate NaN inputs back to the caller and +// otherwise returns the input with a larger magnitude. +// It treats +0 as larger than -0 as per the specification. +// +// Arguments: +// x - left operand +// y - right operand +// +// Return Value: +// Either x or y +// +float FloatingPointUtils::maximumMagnitudeNumber(float x, float y) +{ + float ax = fabsf(x); + float ay = fabsf(y); + + if ((ax > ay) || isNaN(ay)) + { + return x; + } + + if (ax == ay) + { + return isNegative(x) ? y : x; + } + + return y; +} + +//------------------------------------------------------------------------ +// maximumNumber: This matches the IEEE 754:2019 `maximumNumber` function +// +// It does not propagate NaN inputs back to the caller and +// otherwise returns the larger of the inputs. It +// treats +0 as larger than -0 as per the specification. +// +// Arguments: +// x - left operand +// y - right operand +// +// Return Value: +// Either x or y +// +float FloatingPointUtils::maximumNumber(float x, float y) +{ + if (x != y) + { + if (!isNaN(y)) + { + return y < x ? x : y; + } + + return x; + } + + return isNegative(y) ? x : y; +} + //------------------------------------------------------------------------ // minimum: This matches the IEEE 754:2019 `minimum` function -// It propagates NaN inputs back to the caller and -// otherwise returns the larger of the inputs. It -// treats +0 as larger than -0 as per the specification. +// +// It propagates NaN inputs back to the caller and +// otherwise returns the lesser of the inputs. It +// treats +0 as lesser than -0 as per the specification. // // Arguments: // val1 - left operand @@ -2488,21 +2679,120 @@ float FloatingPointUtils::maximum(float val1, float val2) // Return Value: // Either val1 or val2 // - double FloatingPointUtils::minimum(double val1, double val2) { - if (val1 != val2 && !isNaN(val1)) + if (val1 != val2) { - return val1 < val2 ? val1 : val2; + if (!isNaN(val1)) + { + return val1 < val2 ? val1 : val2; + } + + return val1; } + return isNegative(val1) ? val1 : val2; } +//------------------------------------------------------------------------ +// minimumMagnitude: This matches the IEEE 754:2019 `minimumMagnitude` function +// +// It propagates NaN inputs back to the caller and +// otherwise returns the input with a lesser magnitude. +// It treats +0 as lesser than -0 as per the specification. +// +// Arguments: +// x - left operand +// y - right operand +// +// Return Value: +// Either x or y +// +double FloatingPointUtils::minimumMagnitude(double x, double y) +{ + double ax = fabs(x); + double ay = fabs(y); + + if ((ax < ay) || isNaN(ax)) + { + return x; + } + + if (ax == ay) + { + return isNegative(x) ? x : y; + } + + return y; +} + +//------------------------------------------------------------------------ +// minimumMagnitudeNumber: This matches the IEEE 754:2019 `minimumMagnitudeNumber` function +// +// It does not propagate NaN inputs back to the caller and +// otherwise returns the input with a larger magnitude. +// It treats +0 as larger than -0 as per the specification. +// +// Arguments: +// x - left operand +// y - right operand +// +// Return Value: +// Either x or y +// +double FloatingPointUtils::minimumMagnitudeNumber(double x, double y) +{ + double ax = fabs(x); + double ay = fabs(y); + + if ((ax < ay) || isNaN(ay)) + { + return x; + } + + if (ax == ay) + { + return isNegative(x) ? x : y; + } + + return y; +} + +//------------------------------------------------------------------------ +// minimumNumber: This matches the IEEE 754:2019 `minimumNumber` function +// +// It does not propagate NaN inputs back to the caller and +// otherwise returns the larger of the inputs. It +// treats +0 as larger than -0 as per the specification. +// +// Arguments: +// x - left operand +// y - right operand +// +// Return Value: +// Either x or y +// +double FloatingPointUtils::minimumNumber(double x, double y) +{ + if (x != y) + { + if (!isNaN(y)) + { + return x < y ? x : y; + } + + return x; + } + + return isNegative(x) ? x : y; +} + //------------------------------------------------------------------------ // minimum: This matches the IEEE 754:2019 `minimum` function -// It propagates NaN inputs back to the caller and -// otherwise returns the larger of the inputs. It -// treats +0 as larger than -0 as per the specification. +// +// It propagates NaN inputs back to the caller and +// otherwise returns the lesser of the inputs. It +// treats +0 as lesser than -0 as per the specification. // // Arguments: // val1 - left operand @@ -2513,13 +2803,112 @@ double FloatingPointUtils::minimum(double val1, double val2) // float FloatingPointUtils::minimum(float val1, float val2) { - if (val1 != val2 && !isNaN(val1)) + if (val1 != val2) { - return val1 < val2 ? val1 : val2; + if (!isNaN(val1)) + { + return val1 < val2 ? val1 : val2; + } + + return val1; } + return isNegative(val1) ? val1 : val2; } +//------------------------------------------------------------------------ +// minimumMagnitude: This matches the IEEE 754:2019 `minimumMagnitude` function +// +// It propagates NaN inputs back to the caller and +// otherwise returns the input with a lesser magnitude. +// It treats +0 as lesser than -0 as per the specification. +// +// Arguments: +// x - left operand +// y - right operand +// +// Return Value: +// Either x or y +// +float FloatingPointUtils::minimumMagnitude(float x, float y) +{ + float ax = fabsf(x); + float ay = fabsf(y); + + if ((ax < ay) || isNaN(ax)) + { + return x; + } + + if (ax == ay) + { + return isNegative(x) ? x : y; + } + + return y; +} + +//------------------------------------------------------------------------ +// minimumMagnitudeNumber: This matches the IEEE 754:2019 `minimumMagnitudeNumber` function +// +// It does not propagate NaN inputs back to the caller and +// otherwise returns the input with a larger magnitude. +// It treats +0 as larger than -0 as per the specification. +// +// Arguments: +// x - left operand +// y - right operand +// +// Return Value: +// Either x or y +// +float FloatingPointUtils::minimumMagnitudeNumber(float x, float y) +{ + float ax = fabsf(x); + float ay = fabsf(y); + + if ((ax < ay) || isNaN(ay)) + { + return x; + } + + if (ax == ay) + { + return isNegative(x) ? x : y; + } + + return y; +} + +//------------------------------------------------------------------------ +// minimumNumber: This matches the IEEE 754:2019 `minimumNumber` function +// +// It does not propagate NaN inputs back to the caller and +// otherwise returns the larger of the inputs. It +// treats +0 as larger than -0 as per the specification. +// +// Arguments: +// x - left operand +// y - right operand +// +// Return Value: +// Either x or y +// +float FloatingPointUtils::minimumNumber(float x, float y) +{ + if (x != y) + { + if (!isNaN(y)) + { + return x < y ? x : y; + } + + return x; + } + + return isNegative(x) ? x : y; +} + //------------------------------------------------------------------------ // normalize: Normalize a floating point value. // diff --git a/src/coreclr/jit/utils.h b/src/coreclr/jit/utils.h index c4b2832994a768..c14b2b4fed0353 100644 --- a/src/coreclr/jit/utils.h +++ b/src/coreclr/jit/utils.h @@ -763,12 +763,36 @@ class FloatingPointUtils static double maximum(double val1, double val2); + static double maximumMagnitude(double val1, double val2); + + static double maximumMagnitudeNumber(double val1, double val2); + + static double maximumNumber(double val1, double val2); + static float maximum(float val1, float val2); + static float maximumMagnitude(float val1, float val2); + + static float maximumMagnitudeNumber(float val1, float val2); + + static float maximumNumber(float val1, float val2); + static double minimum(double val1, double val2); + static double minimumMagnitude(double val1, double val2); + + static double minimumMagnitudeNumber(double val1, double val2); + + static double minimumNumber(double val1, double val2); + static float minimum(float val1, float val2); + static float minimumMagnitude(float val1, float val2); + + static float minimumMagnitudeNumber(float val1, float val2); + + static float minimumNumber(float val1, float val2); + static double normalize(double x); }; diff --git a/src/coreclr/jit/valuenum.cpp b/src/coreclr/jit/valuenum.cpp index 9951fc1625dc50..51b97de0c54509 100644 --- a/src/coreclr/jit/valuenum.cpp +++ b/src/coreclr/jit/valuenum.cpp @@ -8357,6 +8357,30 @@ ValueNum ValueNumStore::EvalMathFuncBinary(var_types typ, NamedIntrinsic gtMathF break; } + case NI_System_Math_MaxMagnitude: + { + assert(typ == TypeOfVN(arg1VN)); + double arg1Val = GetConstantDouble(arg1VN); + res = FloatingPointUtils::maximumMagnitude(arg0Val, arg1Val); + break; + } + + case NI_System_Math_MaxMagnitudeNumber: + { + assert(typ == TypeOfVN(arg1VN)); + double arg1Val = GetConstantDouble(arg1VN); + res = FloatingPointUtils::maximumMagnitudeNumber(arg0Val, arg1Val); + break; + } + + case NI_System_Math_MaxNumber: + { + assert(typ == TypeOfVN(arg1VN)); + double arg1Val = GetConstantDouble(arg1VN); + res = FloatingPointUtils::maximumNumber(arg0Val, arg1Val); + break; + } + case NI_System_Math_Min: { assert(typ == TypeOfVN(arg1VN)); @@ -8365,6 +8389,30 @@ ValueNum ValueNumStore::EvalMathFuncBinary(var_types typ, NamedIntrinsic gtMathF break; } + case NI_System_Math_MinMagnitude: + { + assert(typ == TypeOfVN(arg1VN)); + double arg1Val = GetConstantDouble(arg1VN); + res = FloatingPointUtils::minimumMagnitude(arg0Val, arg1Val); + break; + } + + case NI_System_Math_MinMagnitudeNumber: + { + assert(typ == TypeOfVN(arg1VN)); + double arg1Val = GetConstantDouble(arg1VN); + res = FloatingPointUtils::minimumMagnitudeNumber(arg0Val, arg1Val); + break; + } + + case NI_System_Math_MinNumber: + { + assert(typ == TypeOfVN(arg1VN)); + double arg1Val = GetConstantDouble(arg1VN); + res = FloatingPointUtils::minimumNumber(arg0Val, arg1Val); + break; + } + default: // the above are the only binary math intrinsics at the time of this writing. unreached(); @@ -8448,10 +8496,34 @@ ValueNum ValueNumStore::EvalMathFuncBinary(var_types typ, NamedIntrinsic gtMathF vnf = VNF_Max; break; + case NI_System_Math_MaxMagnitude: + vnf = VNF_MaxMagnitude; + break; + + case NI_System_Math_MaxMagnitudeNumber: + vnf = VNF_MaxMagnitudeNumber; + break; + + case NI_System_Math_MaxNumber: + vnf = VNF_MaxNumber; + break; + case NI_System_Math_Min: vnf = VNF_Min; break; + case NI_System_Math_MinMagnitude: + vnf = VNF_MinMagnitude; + break; + + case NI_System_Math_MinMagnitudeNumber: + vnf = VNF_MinMagnitudeNumber; + break; + + case NI_System_Math_MinNumber: + vnf = VNF_MinNumber; + break; + case NI_System_Math_Pow: vnf = VNF_Pow; break; diff --git a/src/coreclr/jit/valuenumfuncs.h b/src/coreclr/jit/valuenumfuncs.h index de0c347591a8d2..f875f3aadaf43d 100644 --- a/src/coreclr/jit/valuenumfuncs.h +++ b/src/coreclr/jit/valuenumfuncs.h @@ -92,7 +92,13 @@ ValueNumFuncDef(Log, 1, false, false, false, false) ValueNumFuncDef(Log2, 1, false, false, false, false) ValueNumFuncDef(Log10, 1, false, false, false, false) ValueNumFuncDef(Max, 2, false, false, false, false) +ValueNumFuncDef(MaxMagnitude, 2, false, false, false, false) +ValueNumFuncDef(MaxMagnitudeNumber, 2, false, false, false, false) +ValueNumFuncDef(MaxNumber, 2, false, false, false, false) ValueNumFuncDef(Min, 2, false, false, false, false) +ValueNumFuncDef(MinMagnitude, 2, false, false, false, false) +ValueNumFuncDef(MinMagnitudeNumber, 2, false, false, false, false) +ValueNumFuncDef(MinNumber, 2, false, false, false, false) ValueNumFuncDef(Pow, 2, false, false, false, false) ValueNumFuncDef(RoundDouble, 1, false, false, false, false) ValueNumFuncDef(RoundInt32, 1, false, false, false, false) diff --git a/src/libraries/System.Private.CoreLib/src/System/Double.cs b/src/libraries/System.Private.CoreLib/src/System/Double.cs index e51971b3358bd5..ab94897008cdd9 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Double.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Double.cs @@ -968,6 +968,7 @@ bool IFloatingPoint.TryWriteSignificandLittleEndian(Span destinati public static double Max(double x, double y) => Math.Max(x, y); /// + [Intrinsic] public static double MaxNumber(double x, double y) { // This matches the IEEE 754:2019 `maximumNumber` function @@ -994,6 +995,7 @@ public static double MaxNumber(double x, double y) public static double Min(double x, double y) => Math.Min(x, y); /// + [Intrinsic] public static double MinNumber(double x, double y) { // This matches the IEEE 754:2019 `minimumNumber` function @@ -1128,9 +1130,11 @@ public static bool IsRealNumber(double value) static bool INumberBase.IsZero(double value) => (value == 0); /// + [Intrinsic] public static double MaxMagnitude(double x, double y) => Math.MaxMagnitude(x, y); /// + [Intrinsic] public static double MaxMagnitudeNumber(double x, double y) { // This matches the IEEE 754:2019 `maximumMagnitudeNumber` function @@ -1156,9 +1160,11 @@ public static double MaxMagnitudeNumber(double x, double y) } /// + [Intrinsic] public static double MinMagnitude(double x, double y) => Math.MinMagnitude(x, y); /// + [Intrinsic] public static double MinMagnitudeNumber(double x, double y) { // This matches the IEEE 754:2019 `minimumMagnitudeNumber` function diff --git a/src/libraries/System.Private.CoreLib/src/System/Math.cs b/src/libraries/System.Private.CoreLib/src/System/Math.cs index c1b4273eb272ec..a8f016803b5677 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Math.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Math.cs @@ -995,6 +995,7 @@ public static nuint Max(nuint val1, nuint val2) return (val1 >= val2) ? val1 : val2; } + [Intrinsic] public static double MaxMagnitude(double x, double y) { // This matches the IEEE 754:2019 `maximumMagnitude` function @@ -1144,6 +1145,7 @@ public static nuint Min(nuint val1, nuint val2) return (val1 <= val2) ? val1 : val2; } + [Intrinsic] public static double MinMagnitude(double x, double y) { // This matches the IEEE 754:2019 `minimumMagnitude` function diff --git a/src/libraries/System.Private.CoreLib/src/System/MathF.cs b/src/libraries/System.Private.CoreLib/src/System/MathF.cs index 6179add0dc8a7d..fcd46990bb4648 100644 --- a/src/libraries/System.Private.CoreLib/src/System/MathF.cs +++ b/src/libraries/System.Private.CoreLib/src/System/MathF.cs @@ -250,6 +250,7 @@ public static float Max(float x, float y) return Math.Max(x, y); } + [Intrinsic] public static float MaxMagnitude(float x, float y) { // This matches the IEEE 754:2019 `maximumMagnitude` function @@ -281,6 +282,7 @@ public static float Min(float x, float y) return Math.Min(x, y); } + [Intrinsic] public static float MinMagnitude(float x, float y) { // This matches the IEEE 754:2019 `minimumMagnitude` function diff --git a/src/libraries/System.Private.CoreLib/src/System/Single.cs b/src/libraries/System.Private.CoreLib/src/System/Single.cs index 79f8e96e2371a0..38f5e894135c71 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Single.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Single.cs @@ -948,6 +948,7 @@ bool IFloatingPoint.TryWriteSignificandLittleEndian(Span destinatio public static float Max(float x, float y) => MathF.Max(x, y); /// + [Intrinsic] public static float MaxNumber(float x, float y) { // This matches the IEEE 754:2019 `maximumNumber` function @@ -974,6 +975,7 @@ public static float MaxNumber(float x, float y) public static float Min(float x, float y) => MathF.Min(x, y); /// + [Intrinsic] public static float MinNumber(float x, float y) { // This matches the IEEE 754:2019 `minimumNumber` function @@ -1108,9 +1110,11 @@ public static bool IsRealNumber(float value) static bool INumberBase.IsZero(float value) => (value == 0); /// + [Intrinsic] public static float MaxMagnitude(float x, float y) => MathF.MaxMagnitude(x, y); /// + [Intrinsic] public static float MaxMagnitudeNumber(float x, float y) { // This matches the IEEE 754:2019 `maximumMagnitudeNumber` function @@ -1136,9 +1140,11 @@ public static float MaxMagnitudeNumber(float x, float y) } /// + [Intrinsic] public static float MinMagnitude(float x, float y) => MathF.MinMagnitude(x, y); /// + [Intrinsic] public static float MinMagnitudeNumber(float x, float y) { // This matches the IEEE 754:2019 `minimumMagnitudeNumber` function From c4a6fcc01a950193d76e667bf3833929f46f5908 Mon Sep 17 00:00:00 2001 From: Tanner Gooding Date: Thu, 15 Jun 2023 17:57:50 -0700 Subject: [PATCH 4/6] Don't include ILogB for the time being --- src/libraries/System.Private.CoreLib/src/System/Double.cs | 1 - src/libraries/System.Private.CoreLib/src/System/Math.cs | 1 - src/libraries/System.Private.CoreLib/src/System/MathF.cs | 1 - src/libraries/System.Private.CoreLib/src/System/Single.cs | 1 - 4 files changed, 4 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/Double.cs b/src/libraries/System.Private.CoreLib/src/System/Double.cs index ab94897008cdd9..9058eaecf4f238 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Double.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Double.cs @@ -845,7 +845,6 @@ bool IFloatingPoint.TryWriteSignificandLittleEndian(Span destinati public static double Ieee754Remainder(double left, double right) => Math.IEEERemainder(left, right); /// - [Intrinsic] public static int ILogB(double x) => Math.ILogB(x); /// diff --git a/src/libraries/System.Private.CoreLib/src/System/Math.cs b/src/libraries/System.Private.CoreLib/src/System/Math.cs index a8f016803b5677..db55090f80f84a 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Math.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Math.cs @@ -812,7 +812,6 @@ public static double IEEERemainder(double x, double y) } } - [Intrinsic] public static int ILogB(double x) { // Implementation based on https://git.musl-libc.org/cgit/musl/tree/src/math/ilogb.c diff --git a/src/libraries/System.Private.CoreLib/src/System/MathF.cs b/src/libraries/System.Private.CoreLib/src/System/MathF.cs index fcd46990bb4648..17f466bae27908 100644 --- a/src/libraries/System.Private.CoreLib/src/System/MathF.cs +++ b/src/libraries/System.Private.CoreLib/src/System/MathF.cs @@ -185,7 +185,6 @@ public static float IEEERemainder(float x, float y) } } - [Intrinsic] public static int ILogB(float x) { // Implementation based on https://git.musl-libc.org/cgit/musl/tree/src/math/ilogbf.c diff --git a/src/libraries/System.Private.CoreLib/src/System/Single.cs b/src/libraries/System.Private.CoreLib/src/System/Single.cs index 38f5e894135c71..42f5861c3be712 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Single.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Single.cs @@ -825,7 +825,6 @@ bool IFloatingPoint.TryWriteSignificandLittleEndian(Span destinatio public static float Ieee754Remainder(float left, float right) => MathF.IEEERemainder(left, right); /// - [Intrinsic] public static int ILogB(float x) => MathF.ILogB(x); /// From 0e6353c8244bbf06bac2a5af300739c5b5a862db Mon Sep 17 00:00:00 2001 From: Tanner Gooding Date: Thu, 15 Jun 2023 20:31:27 -0700 Subject: [PATCH 5/6] Ensure FixupScalar is passed all 4 parameters --- src/coreclr/jit/importercalls.cpp | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/coreclr/jit/importercalls.cpp b/src/coreclr/jit/importercalls.cpp index 03684a70ce65c7..8962298e4b9f5d 100644 --- a/src/coreclr/jit/importercalls.cpp +++ b/src/coreclr/jit/importercalls.cpp @@ -8202,8 +8202,8 @@ GenTree* Compiler::impMinMaxIntrinsic(CORINFO_METHOD_HANDLE method, tbl->gtSimdVal.i32[0] = 0x0700; } - retNode = gtNewSimdHWIntrinsicNode(TYP_SIMD16, retNode, op2Clone, tbl, NI_AVX512F_FixupScalar, - callJitType, 16); + retNode = gtNewSimdHWIntrinsicNode(TYP_SIMD16, retNode, op2Clone, tbl, gtNewIconNode(0), + NI_AVX512F_FixupScalar, callJitType, 16); } if (isNumber) @@ -8329,10 +8329,11 @@ GenTree* Compiler::impMinMaxIntrinsic(CORINFO_METHOD_HANDLE method, tbl1 = impCloneExpr(tbl1, &tbl2, CHECK_SPILL_ALL, nullptr DEBUGARG("Cloning tbl for Math.Max/Min")); - op1Clone = - gtNewSimdHWIntrinsicNode(TYP_SIMD16, op1Clone, op2Clone, tbl1, NI_AVX512F_FixupScalar, callJitType, 16); + op1Clone = gtNewSimdHWIntrinsicNode(TYP_SIMD16, op1Clone, op2Clone, tbl1, gtNewIconNode(0), + NI_AVX512F_FixupScalar, callJitType, 16); - tmp = gtNewSimdHWIntrinsicNode(TYP_SIMD16, op1Clone, tmp, tbl2, NI_AVX512F_FixupScalar, callJitType, 16); + tmp = gtNewSimdHWIntrinsicNode(TYP_SIMD16, op1Clone, tmp, tbl2, gtNewIconNode(0), NI_AVX512F_FixupScalar, + callJitType, 16); } else { @@ -8371,10 +8372,11 @@ GenTree* Compiler::impMinMaxIntrinsic(CORINFO_METHOD_HANDLE method, tbl1 = impCloneExpr(tbl1, &tbl2, CHECK_SPILL_ALL, nullptr DEBUGARG("Cloning tbl for Math.Max/Min")); - op1Clone = - gtNewSimdHWIntrinsicNode(TYP_SIMD16, op1Clone, op2Clone, tbl1, NI_AVX512F_FixupScalar, callJitType, 16); + op1Clone = gtNewSimdHWIntrinsicNode(TYP_SIMD16, op1Clone, op2Clone, tbl1, gtNewIconNode(0), + NI_AVX512F_FixupScalar, callJitType, 16); - tmp = gtNewSimdHWIntrinsicNode(TYP_SIMD16, tmp, op1Clone, tbl2, NI_AVX512F_FixupScalar, callJitType, 16); + tmp = gtNewSimdHWIntrinsicNode(TYP_SIMD16, tmp, op1Clone, tbl2, gtNewIconNode(0), NI_AVX512F_FixupScalar, + callJitType, 16); } return gtNewSimdHWIntrinsicNode(callType, tmp, NI_Vector128_ToScalar, callJitType, 16); From 5f10143d186c6a573eff2bee06fed2a8f1511b70 Mon Sep 17 00:00:00 2001 From: Tanner Gooding Date: Mon, 19 Jun 2023 14:57:07 -0700 Subject: [PATCH 6/6] Apply suggestions from code review --- src/coreclr/jit/importercalls.cpp | 48 +++++++++++++++---------------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/src/coreclr/jit/importercalls.cpp b/src/coreclr/jit/importercalls.cpp index 8962298e4b9f5d..0d8e928c56580b 100644 --- a/src/coreclr/jit/importercalls.cpp +++ b/src/coreclr/jit/importercalls.cpp @@ -3401,9 +3401,9 @@ GenTree* Compiler::impIntrinsic(GenTree* newobjThis, case NI_System_Math_Max: { - bool isMax = true; - bool isMagnitude = false; - bool isNumber = false; + const bool isMax = true; + const bool isMagnitude = false; + const bool isNumber = false; retNode = impMinMaxIntrinsic(method, sig, callJitType, ni, tailCall, isMax, isMagnitude, isNumber); break; @@ -3411,9 +3411,9 @@ GenTree* Compiler::impIntrinsic(GenTree* newobjThis, case NI_System_Math_Min: { - bool isMax = false; - bool isMagnitude = false; - bool isNumber = false; + const bool isMax = false; + const bool isMagnitude = false; + const bool isNumber = false; retNode = impMinMaxIntrinsic(method, sig, callJitType, ni, tailCall, isMax, isMagnitude, isNumber); break; @@ -3421,9 +3421,9 @@ GenTree* Compiler::impIntrinsic(GenTree* newobjThis, case NI_System_Math_MaxMagnitude: { - bool isMax = true; - bool isMagnitude = true; - bool isNumber = false; + const bool isMax = true; + const bool isMagnitude = true; + const bool isNumber = false; retNode = impMinMaxIntrinsic(method, sig, callJitType, ni, tailCall, isMax, isMagnitude, isNumber); break; @@ -3431,9 +3431,9 @@ GenTree* Compiler::impIntrinsic(GenTree* newobjThis, case NI_System_Math_MinMagnitude: { - bool isMax = false; - bool isMagnitude = true; - bool isNumber = false; + const bool isMax = false; + const bool isMagnitude = true; + const bool isNumber = false; retNode = impMinMaxIntrinsic(method, sig, callJitType, ni, tailCall, isMax, isMagnitude, isNumber); break; @@ -3441,9 +3441,9 @@ GenTree* Compiler::impIntrinsic(GenTree* newobjThis, case NI_System_Math_MaxMagnitudeNumber: { - bool isMax = true; - bool isMagnitude = true; - bool isNumber = true; + const bool isMax = true; + const bool isMagnitude = true; + const bool isNumber = true; retNode = impMinMaxIntrinsic(method, sig, callJitType, ni, tailCall, isMax, isMagnitude, isNumber); break; @@ -3451,9 +3451,9 @@ GenTree* Compiler::impIntrinsic(GenTree* newobjThis, case NI_System_Math_MinMagnitudeNumber: { - bool isMax = false; - bool isMagnitude = true; - bool isNumber = true; + const bool isMax = false; + const bool isMagnitude = true; + const bool isNumber = true; retNode = impMinMaxIntrinsic(method, sig, callJitType, ni, tailCall, isMax, isMagnitude, isNumber); break; @@ -3461,9 +3461,9 @@ GenTree* Compiler::impIntrinsic(GenTree* newobjThis, case NI_System_Math_MaxNumber: { - bool isMax = true; - bool isMagnitude = false; - bool isNumber = true; + const bool isMax = true; + const bool isMagnitude = false; + const bool isNumber = true; retNode = impMinMaxIntrinsic(method, sig, callJitType, ni, tailCall, isMax, isMagnitude, isNumber); break; @@ -3471,9 +3471,9 @@ GenTree* Compiler::impIntrinsic(GenTree* newobjThis, case NI_System_Math_MinNumber: { - bool isMax = false; - bool isMagnitude = false; - bool isNumber = true; + const bool isMax = false; + const bool isMagnitude = false; + const bool isNumber = true; retNode = impMinMaxIntrinsic(method, sig, callJitType, ni, tailCall, isMax, isMagnitude, isNumber); break;