diff --git a/src/coreclr/jit/codegenxarch.cpp b/src/coreclr/jit/codegenxarch.cpp index d4173c557ac5c8..5050cab0b57815 100644 --- a/src/coreclr/jit/codegenxarch.cpp +++ b/src/coreclr/jit/codegenxarch.cpp @@ -490,19 +490,24 @@ void CodeGen::genSetRegToConst(regNumber targetReg, var_types targetType, GenTre case GT_CNS_DBL: { - emitter* emit = GetEmitter(); - emitAttr size = emitTypeSize(targetType); - double constValue = tree->AsDblCon()->gtDconVal; + emitter* emit = GetEmitter(); + emitAttr size = emitTypeSize(targetType); - // Make sure we use "xorps reg, reg" only for +ve zero constant (0.0) and not for -ve zero (-0.0) - if (*(__int64*)&constValue == 0) + if (tree->IsFloatPositiveZero()) { - // A faster/smaller way to generate 0 + // A faster/smaller way to generate Zero emit->emitIns_R_R(INS_xorps, size, targetReg, targetReg); } + else if (tree->IsFloatAllBitsSet()) + { + // A faster/smaller way to generate AllBitsSet + emit->emitIns_R_R(INS_pcmpeqd, size, targetReg, targetReg); + } else { - CORINFO_FIELD_HANDLE hnd = emit->emitFltOrDblConst(constValue, size); + double cns = tree->AsDblCon()->gtDconVal; + CORINFO_FIELD_HANDLE hnd = emit->emitFltOrDblConst(cns, size); + emit->emitIns_R_C(ins_Load(targetType), size, targetReg, hnd, 0); } } diff --git a/src/coreclr/jit/compiler.h b/src/coreclr/jit/compiler.h index 31fb08457cd5b9..ab5d9fea85b76b 100644 --- a/src/coreclr/jit/compiler.h +++ b/src/coreclr/jit/compiler.h @@ -4872,6 +4872,9 @@ class Compiler // Does value-numbering for a cast tree. void fgValueNumberCastTree(GenTree* tree); + // Does value-numbering for a bitcast tree. + void fgValueNumberBitCast(GenTree* tree); + // Does value-numbering for an intrinsic tree. void fgValueNumberIntrinsic(GenTree* tree); diff --git a/src/coreclr/jit/gentree.cpp b/src/coreclr/jit/gentree.cpp index ae7583871a2bc7..77b6bcf8f36eea 100644 --- a/src/coreclr/jit/gentree.cpp +++ b/src/coreclr/jit/gentree.cpp @@ -4567,9 +4567,10 @@ unsigned Compiler::gtSetEvalOrder(GenTree* tree) { level = 0; #if defined(TARGET_XARCH) - if (tree->IsFloatPositiveZero()) + if (tree->IsFloatPositiveZero() || tree->IsFloatAllBitsSet()) { - // We generate `xorp* tgtReg, tgtReg` which is 3-5 bytes + // We generate `xorp* tgtReg, tgtReg` for PositiveZero and + // `pcmpeqd tgtReg, tgtReg` for AllBitsSet which is 3-5 bytes // but which can be elided by the instruction decoder. costEx = 1; diff --git a/src/coreclr/jit/gentree.h b/src/coreclr/jit/gentree.h index f180d0fbe70c1b..d00621bcd3fa5e 100644 --- a/src/coreclr/jit/gentree.h +++ b/src/coreclr/jit/gentree.h @@ -1828,6 +1828,7 @@ struct GenTree #endif // DEBUG inline bool IsIntegralConst(ssize_t constVal) const; + inline bool IsFloatAllBitsSet() const; inline bool IsFloatNaN() const; inline bool IsFloatPositiveZero() const; inline bool IsFloatNegativeZero() const; @@ -8458,6 +8459,33 @@ inline bool GenTree::IsIntegralConst(ssize_t constVal) const return false; } +//------------------------------------------------------------------- +// IsFloatAllBitsSet: returns true if this is exactly a const float value representing AllBitsSet. +// +// Returns: +// True if this represents a const floating-point value representing AllBitsSet. +// Will return false otherwise. +// +inline bool GenTree::IsFloatAllBitsSet() const +{ + if (IsCnsFltOrDbl()) + { + double constValue = AsDblCon()->gtDconVal; + + if (TypeIs(TYP_FLOAT)) + { + return FloatingPointUtils::isAllBitsSet(static_cast(constValue)); + } + else + { + assert(TypeIs(TYP_DOUBLE)); + return FloatingPointUtils::isAllBitsSet(constValue); + } + } + + return false; +} + //------------------------------------------------------------------- // IsFloatNaN: returns true if this is exactly a const float value of NaN // @@ -8509,7 +8537,7 @@ inline bool GenTree::IsFloatPositiveZero() const // but it is easier to parse out // rather than using !IsCnsNonZeroFltOrDbl. double constValue = AsDblCon()->gtDconVal; - return *(__int64*)&constValue == 0; + return FloatingPointUtils::isPositiveZero(constValue); } return false; diff --git a/src/coreclr/jit/importer.cpp b/src/coreclr/jit/importer.cpp index 697cd838dc4d58..7e10dfa6602c69 100644 --- a/src/coreclr/jit/importer.cpp +++ b/src/coreclr/jit/importer.cpp @@ -4880,6 +4880,96 @@ GenTree* Compiler::impIntrinsic(GenTree* newobjThis, break; } + case NI_System_BitConverter_DoubleToInt64Bits: + { + GenTree* op1 = impStackTop().val; + assert(varTypeIsFloating(op1)); + + if (op1->IsCnsFltOrDbl()) + { + impPopStack(); + + double f64Cns = op1->AsDblCon()->gtDconVal; + retNode = gtNewLconNode(*reinterpret_cast(&f64Cns)); + } +#if TARGET_64BIT + else + { + // TODO-Cleanup: We should support this on 32-bit but it requires decomposition work + impPopStack(); + + if (op1->TypeGet() != TYP_DOUBLE) + { + op1 = gtNewCastNode(TYP_DOUBLE, op1, false, TYP_DOUBLE); + } + retNode = gtNewBitCastNode(TYP_LONG, op1); + } +#endif + break; + } + + case NI_System_BitConverter_Int32BitsToSingle: + { + GenTree* op1 = impPopStack().val; + assert(varTypeIsInt(op1)); + + if (op1->IsIntegralConst()) + { + int32_t i32Cns = (int32_t)op1->AsIntConCommon()->IconValue(); + retNode = gtNewDconNode(*reinterpret_cast(&i32Cns), TYP_FLOAT); + } + else + { + retNode = gtNewBitCastNode(TYP_FLOAT, op1); + } + break; + } + + case NI_System_BitConverter_Int64BitsToDouble: + { + GenTree* op1 = impStackTop().val; + assert(varTypeIsLong(op1)); + + if (op1->IsIntegralConst()) + { + impPopStack(); + + int64_t i64Cns = op1->AsIntConCommon()->LngValue(); + retNode = gtNewDconNode(*reinterpret_cast(&i64Cns)); + } +#if TARGET_64BIT + else + { + // TODO-Cleanup: We should support this on 32-bit but it requires decomposition work + impPopStack(); + + retNode = gtNewBitCastNode(TYP_DOUBLE, op1); + } +#endif + break; + } + + case NI_System_BitConverter_SingleToInt32Bits: + { + GenTree* op1 = impPopStack().val; + assert(varTypeIsFloating(op1)); + + if (op1->IsCnsFltOrDbl()) + { + float f32Cns = (float)op1->AsDblCon()->gtDconVal; + retNode = gtNewIconNode(*reinterpret_cast(&f32Cns)); + } + else + { + if (op1->TypeGet() != TYP_FLOAT) + { + op1 = gtNewCastNode(TYP_FLOAT, op1, false, TYP_FLOAT); + } + retNode = gtNewBitCastNode(TYP_INT, op1); + } + break; + } + default: break; } @@ -5518,6 +5608,53 @@ NamedIntrinsic Compiler::lookupNamedIntrinsic(CORINFO_METHOD_HANDLE method) result = NI_System_Activator_DefaultConstructorOf; } } + else if (strcmp(className, "BitConverter") == 0) + { + if (methodName[0] == 'D') + { + if (strcmp(methodName, "DoubleToInt64Bits") == 0) + { + result = NI_System_BitConverter_DoubleToInt64Bits; + } + else if (strcmp(methodName, "DoubleToUInt64Bits") == 0) + { + result = NI_System_BitConverter_DoubleToInt64Bits; + } + } + else if (methodName[0] == 'I') + { + if (strcmp(methodName, "Int32BitsToSingle") == 0) + { + result = NI_System_BitConverter_Int32BitsToSingle; + } + else if (strcmp(methodName, "Int64BitsToDouble") == 0) + { + result = NI_System_BitConverter_Int64BitsToDouble; + } + } + else if (methodName[0] == 'S') + { + if (strcmp(methodName, "SingleToInt32Bits") == 0) + { + result = NI_System_BitConverter_SingleToInt32Bits; + } + else if (strcmp(methodName, "SingleToUInt32Bits") == 0) + { + result = NI_System_BitConverter_SingleToInt32Bits; + } + } + else if (methodName[0] == 'U') + { + if (strcmp(methodName, "UInt32BitsToSingle") == 0) + { + result = NI_System_BitConverter_Int32BitsToSingle; + } + else if (strcmp(methodName, "UInt64BitsToDouble") == 0) + { + result = NI_System_BitConverter_Int64BitsToDouble; + } + } + } else if (strcmp(className, "Math") == 0 || strcmp(className, "MathF") == 0) { if (strcmp(methodName, "Abs") == 0) diff --git a/src/coreclr/jit/namedintrinsiclist.h b/src/coreclr/jit/namedintrinsiclist.h index ca812ab66e311e..e33b1f391f6d4e 100644 --- a/src/coreclr/jit/namedintrinsiclist.h +++ b/src/coreclr/jit/namedintrinsiclist.h @@ -15,6 +15,11 @@ enum NamedIntrinsic : unsigned short NI_System_Enum_HasFlag, + NI_System_BitConverter_DoubleToInt64Bits, + NI_System_BitConverter_Int32BitsToSingle, + NI_System_BitConverter_Int64BitsToDouble, + NI_System_BitConverter_SingleToInt32Bits, + NI_SYSTEM_MATH_START, NI_System_Math_Abs, NI_System_Math_Acos, diff --git a/src/coreclr/jit/utils.cpp b/src/coreclr/jit/utils.cpp index 8f3db9050c2d76..5d58995f3377fd 100644 --- a/src/coreclr/jit/utils.cpp +++ b/src/coreclr/jit/utils.cpp @@ -2232,6 +2232,38 @@ bool FloatingPointUtils::hasPreciseReciprocal(float x) return mantissa == 0 && exponent != 0 && exponent != 127; } +//------------------------------------------------------------------------ +// isAllBitsSet: Determines whether the specified value is AllBitsSet +// +// Arguments: +// val - value to check for AllBitsSet +// +// Return Value: +// True if val is AllBitsSet +// + +bool FloatingPointUtils::isAllBitsSet(float val) +{ + UINT32 bits = *reinterpret_cast(&val); + return bits == 0xFFFFFFFFU; +} + +//------------------------------------------------------------------------ +// isAllBitsSet: Determines whether the specified value is AllBitsSet +// +// Arguments: +// val - value to check for AllBitsSet +// +// Return Value: +// True if val is AllBitsSet +// + +bool FloatingPointUtils::isAllBitsSet(double val) +{ + UINT64 bits = *reinterpret_cast(&val); + return bits == 0xFFFFFFFFFFFFFFFFULL; +} + //------------------------------------------------------------------------ // isNegative: Determines whether the specified value is negative // @@ -2310,6 +2342,22 @@ bool FloatingPointUtils::isNegativeZero(double val) return bits == 0x8000000000000000ULL; } +//------------------------------------------------------------------------ +// isPositiveZero: Determines whether the specified value is positive zero (+0.0) +// +// Arguments: +// val - value to check for (+0.0) +// +// Return Value: +// True if val is (+0.0) +// + +bool FloatingPointUtils::isPositiveZero(double val) +{ + UINT64 bits = *reinterpret_cast(&val); + return bits == 0x0000000000000000ULL; +} + //------------------------------------------------------------------------ // maximum: This matches the IEEE 754:2019 `maximum` function // It propagates NaN inputs back to the caller and diff --git a/src/coreclr/jit/utils.h b/src/coreclr/jit/utils.h index a5c40f44e5d0fa..8ae84d0d8858c2 100644 --- a/src/coreclr/jit/utils.h +++ b/src/coreclr/jit/utils.h @@ -703,6 +703,10 @@ class FloatingPointUtils static float infinite_float(); + static bool isAllBitsSet(float val); + + static bool isAllBitsSet(double val); + static bool isNegative(float val); static bool isNegative(double val); @@ -713,6 +717,8 @@ class FloatingPointUtils static bool isNegativeZero(double val); + static bool isPositiveZero(double val); + static double maximum(double val1, double val2); static float maximum(float val1, float val2); diff --git a/src/coreclr/jit/valuenum.cpp b/src/coreclr/jit/valuenum.cpp index a4549964c22e5d..cc8d8fd3d8765f 100644 --- a/src/coreclr/jit/valuenum.cpp +++ b/src/coreclr/jit/valuenum.cpp @@ -6808,6 +6808,7 @@ static genTreeOps genTreeOpsIllegalAsVNFunc[] = {GT_IND, // When we do heap memo GT_INIT_VAL, // Not strictly a pass-through. GT_MDARR_LENGTH, GT_MDARR_LOWER_BOUND, // 'dim' value must be considered + GT_BITCAST, // Needs to encode the target type. // These control-flow operations need no values. GT_JTRUE, GT_RETURN, GT_SWITCH, GT_RETFILT, GT_CKFINITE}; @@ -8972,6 +8973,12 @@ void Compiler::fgValueNumberTree(GenTree* tree) vnStore->VNPExceptionSet(tree->gtGetOp1()->gtVNPair)); break; + case GT_BITCAST: + { + fgValueNumberBitCast(tree); + break; + } + default: assert(!"Unhandled node in fgValueNumberTree"); tree->gtVNPair.SetBoth(vnStore->VNForExpr(compCurBB, tree->TypeGet())); @@ -9574,6 +9581,29 @@ ValueNumPair ValueNumStore::VNPairForCast(ValueNumPair srcVNPair, return {castLibVN, castConVN}; } +//------------------------------------------------------------------------ +// fgValueNumberBitCast: Value number a bitcast. +// +// Arguments: +// tree - The tree performing the bitcast +// +void Compiler::fgValueNumberBitCast(GenTree* tree) +{ + assert(tree->OperGet() == GT_BITCAST); + + ValueNumPair srcVNPair = tree->gtGetOp1()->gtVNPair; + var_types castToType = tree->TypeGet(); + + ValueNumPair srcNormVNPair; + ValueNumPair srcExcVNPair; + vnStore->VNPUnpackExc(srcVNPair, &srcNormVNPair, &srcExcVNPair); + + ValueNumPair resultNormVNPair = vnStore->VNPairForBitCast(srcNormVNPair, castToType); + ValueNumPair resultExcVNPair = srcExcVNPair; + + tree->gtVNPair = vnStore->VNPWithExc(resultNormVNPair, resultExcVNPair); +} + //------------------------------------------------------------------------ // VNForBitCast: Get the VN representing bitwise reinterpretation of types. // @@ -9624,6 +9654,20 @@ ValueNum ValueNumStore::VNForBitCast(ValueNum srcVN, var_types castToType) return VNForFunc(castToType, VNF_BitCast, srcVN, VNForIntCon(castToType)); } +//------------------------------------------------------------------------ +// VNPairForBitCast: VNForBitCast applied to a ValueNumPair. +// +ValueNumPair ValueNumStore::VNPairForBitCast(ValueNumPair srcVNPair, var_types castToType) +{ + ValueNum srcLibVN = srcVNPair.GetLiberal(); + ValueNum srcConVN = srcVNPair.GetConservative(); + + ValueNum bitCastLibVN = VNForBitCast(srcLibVN, castToType); + ValueNum bitCastConVN = VNForBitCast(srcConVN, castToType); + + return ValueNumPair(bitCastLibVN, bitCastConVN); +} + void Compiler::fgValueNumberHelperCallFunc(GenTreeCall* call, VNFunc vnf, ValueNumPair vnpExc) { unsigned nArgs = ValueNumStore::VNFuncArity(vnf); diff --git a/src/coreclr/jit/valuenum.h b/src/coreclr/jit/valuenum.h index e254bb4bf61e6c..f2ea4f2eb1cf86 100644 --- a/src/coreclr/jit/valuenum.h +++ b/src/coreclr/jit/valuenum.h @@ -733,6 +733,7 @@ class ValueNumStore bool hasOverflowCheck = false); ValueNum VNForBitCast(ValueNum srcVN, var_types castToType); + ValueNumPair VNPairForBitCast(ValueNumPair srcVNPair, var_types castToType); bool IsVNNotAField(ValueNum vn); diff --git a/src/libraries/System.Private.CoreLib/src/System/BitConverter.cs b/src/libraries/System.Private.CoreLib/src/System/BitConverter.cs index b5aa38e497d2e7..c85be98dfdd370 100644 --- a/src/libraries/System.Private.CoreLib/src/System/BitConverter.cs +++ b/src/libraries/System.Private.CoreLib/src/System/BitConverter.cs @@ -767,72 +767,32 @@ public static bool ToBoolean(ReadOnlySpan value) /// /// The number to convert. /// A 64-bit signed integer whose bits are identical to . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static unsafe long DoubleToInt64Bits(double value) - { - // Workaround for https://github.com/dotnet/runtime/issues/11413 - if (Sse2.X64.IsSupported) - { - Vector128 vec = Vector128.CreateScalarUnsafe(value).AsInt64(); - return Sse2.X64.ConvertToInt64(vec); - } - - return *((long*)&value); - } + [Intrinsic] + public static unsafe long DoubleToInt64Bits(double value) => *((long*)&value); /// /// Converts the specified 64-bit signed integer to a double-precision floating point number. /// /// The number to convert. /// A double-precision floating point number whose bits are identical to . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static unsafe double Int64BitsToDouble(long value) - { - // Workaround for https://github.com/dotnet/runtime/issues/11413 - if (Sse2.X64.IsSupported) - { - Vector128 vec = Vector128.CreateScalarUnsafe(value).AsDouble(); - return vec.ToScalar(); - } - - return *((double*)&value); - } + [Intrinsic] + public static unsafe double Int64BitsToDouble(long value) => *((double*)&value); /// /// Converts the specified single-precision floating point number to a 32-bit signed integer. /// /// The number to convert. /// A 32-bit signed integer whose bits are identical to . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static unsafe int SingleToInt32Bits(float value) - { - // Workaround for https://github.com/dotnet/runtime/issues/11413 - if (Sse2.IsSupported) - { - Vector128 vec = Vector128.CreateScalarUnsafe(value).AsInt32(); - return Sse2.ConvertToInt32(vec); - } - - return *((int*)&value); - } + [Intrinsic] + public static unsafe int SingleToInt32Bits(float value) => *((int*)&value); /// /// Converts the specified 32-bit signed integer to a single-precision floating point number. /// /// The number to convert. /// A single-precision floating point number whose bits are identical to . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static unsafe float Int32BitsToSingle(int value) - { - // Workaround for https://github.com/dotnet/runtime/issues/11413 - if (Sse2.IsSupported) - { - Vector128 vec = Vector128.CreateScalarUnsafe(value).AsSingle(); - return vec.ToScalar(); - } - - return *((float*)&value); - } + [Intrinsic] + public static unsafe float Int32BitsToSingle(int value) => *((float*)&value); /// /// Converts the specified half-precision floating point number to a 16-bit signed integer. @@ -840,7 +800,7 @@ public static unsafe float Int32BitsToSingle(int value) /// The number to convert. /// A 16-bit signed integer whose bits are identical to . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static unsafe short HalfToInt16Bits(Half value) => (short)HalfToUInt16Bits(value); + public static unsafe short HalfToInt16Bits(Half value) => (short)value._value; /// /// Converts the specified 16-bit signed integer to a half-precision floating point number. @@ -848,7 +808,7 @@ public static unsafe float Int32BitsToSingle(int value) /// The number to convert. /// A half-precision floating point number whose bits are identical to . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static unsafe Half Int16BitsToHalf(short value) => UInt16BitsToHalf((ushort)(value)); + public static unsafe Half Int16BitsToHalf(short value) => new Half((ushort)(value)); /// /// Converts the specified double-precision floating point number to a 64-bit unsigned integer. @@ -856,8 +816,8 @@ public static unsafe float Int32BitsToSingle(int value) /// The number to convert. /// A 64-bit unsigned integer whose bits are identical to . [CLSCompliant(false)] - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static unsafe ulong DoubleToUInt64Bits(double value) => (ulong)DoubleToInt64Bits(value); + [Intrinsic] + public static unsafe ulong DoubleToUInt64Bits(double value) => *((ulong*)&value); /// /// Converts the specified 64-bit unsigned integer to a double-precision floating point number. @@ -865,8 +825,8 @@ public static unsafe float Int32BitsToSingle(int value) /// The number to convert. /// A double-precision floating point number whose bits are identical to . [CLSCompliant(false)] - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static unsafe double UInt64BitsToDouble(ulong value) => Int64BitsToDouble((long)value); + [Intrinsic] + public static unsafe double UInt64BitsToDouble(ulong value) => *((double*)&value); /// /// Converts the specified single-precision floating point number to a 32-bit unsigned integer. @@ -874,8 +834,8 @@ public static unsafe float Int32BitsToSingle(int value) /// The number to convert. /// A 32-bit unsigned integer whose bits are identical to . [CLSCompliant(false)] - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static unsafe uint SingleToUInt32Bits(float value) => (uint)SingleToInt32Bits(value); + [Intrinsic] + public static unsafe uint SingleToUInt32Bits(float value) => *((uint*)&value); /// /// Converts the specified 32-bit unsigned integer to a single-precision floating point number. @@ -883,8 +843,8 @@ public static unsafe float Int32BitsToSingle(int value) /// The number to convert. /// A single-precision floating point number whose bits are identical to . [CLSCompliant(false)] - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static unsafe float UInt32BitsToSingle(uint value) => Int32BitsToSingle((int)value); + [Intrinsic] + public static unsafe float UInt32BitsToSingle(uint value) => *((float*)&value); /// /// Converts the specified half-precision floating point number to a 16-bit unsigned integer.