Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Updating a few BitConverter APIs to be intrinsic #71567

Merged
merged 10 commits into from
Jul 7, 2022
10 changes: 8 additions & 2 deletions src/coreclr/jit/codegenxarch.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -493,13 +493,19 @@ void CodeGen::genSetRegToConst(regNumber targetReg, var_types targetType, GenTre
emitter* emit = GetEmitter();
emitAttr size = emitTypeSize(targetType);
double constValue = tree->AsDblCon()->gtDconVal;
int64_t constBits = *(__int64*)&constValue;

// 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 (constBits == 0)
{
// 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 ((constBits == -1) || ((size == 4) && (constBits == 0xFFFFFFFFE0000000)))
tannergooding marked this conversation as resolved.
Show resolved Hide resolved
{
// 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);
Expand Down
3 changes: 3 additions & 0 deletions src/coreclr/jit/compiler.h
Original file line number Diff line number Diff line change
Expand Up @@ -4804,6 +4804,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);

Expand Down
130 changes: 130 additions & 0 deletions src/coreclr/jit/importer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4911,6 +4911,89 @@ GenTree* Compiler::impIntrinsic(GenTree* newobjThis,
break;
}

case NI_System_BitConverter_DoubleToInt64Bits:
{
GenTree* op1 = impPopStack().val;
assert(varTypeIsFloating(op1));

if (op1->IsCnsFltOrDbl())
{
double f64Cns = op1->AsDblCon()->gtDconVal;
retNode = gtNewLconNode(*(int64_t*)&f64Cns);
tannergooding marked this conversation as resolved.
Show resolved Hide resolved
}
#if TARGET_64BIT
else
{
// TODO-Cleanup: We should support this on 32-bit but it requires decomposition work

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(*(float*)&i32Cns, TYP_FLOAT);
}
else
{
retNode = gtNewBitCastNode(TYP_FLOAT, op1);
}
break;
}

case NI_System_BitConverter_Int64BitsToDouble:
{
GenTree* op1 = impPopStack().val;
assert(varTypeIsLong(op1));

if (op1->IsIntegralConst())
{
int64_t i64Cns = op1->AsIntConCommon()->LngValue();
retNode = gtNewDconNode(*(double*)&i64Cns);
}
#if TARGET_64BIT
else
{
// TODO-Cleanup: We should support this on 32-bit but it requires decomposition work
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(*(int32_t*)&f32Cns);
}
else
{
if (op1->TypeGet() != TYP_FLOAT)
{
op1 = gtNewCastNode(TYP_FLOAT, op1, false, TYP_FLOAT);
}
retNode = gtNewBitCastNode(TYP_INT, op1);
}
break;
}

default:
break;
}
Expand Down Expand Up @@ -5549,6 +5632,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, "ByReference`1") == 0)
{
if (strcmp(methodName, ".ctor") == 0)
Expand Down
5 changes: 5 additions & 0 deletions src/coreclr/jit/namedintrinsiclist.h
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
34 changes: 33 additions & 1 deletion src/coreclr/jit/valuenum.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8737,6 +8737,10 @@ void Compiler::fgValueNumberTree(GenTree* tree)
{
fgValueNumberCastTree(tree);
}
else if (tree->OperGet() == GT_BITCAST)
{
fgValueNumberBitCast(tree);
tannergooding marked this conversation as resolved.
Show resolved Hide resolved
}
else if (tree->OperGet() == GT_INTRINSIC)
{
fgValueNumberIntrinsic(tree);
Expand Down Expand Up @@ -9483,6 +9487,16 @@ void Compiler::fgValueNumberCastTree(GenTree* tree)
tree->gtVNPair = vnStore->VNPairForCast(srcVNPair, castToType, castFromType, srcIsUnsigned, hasOverflowCheck);
}

void Compiler::fgValueNumberBitCast(GenTree* tree)
tannergooding marked this conversation as resolved.
Show resolved Hide resolved
{
assert(tree->OperGet() == GT_BITCAST);

ValueNumPair srcVNPair = tree->gtGetOp1()->gtVNPair;
var_types castToType = tree->TypeGet();

tree->gtVNPair = vnStore->VNPairForBitCast(srcVNPair, castToType);
}

// Compute the ValueNumber for a cast operation
ValueNum ValueNumStore::VNForCast(ValueNum srcVN,
var_types castToType,
Expand Down Expand Up @@ -9584,7 +9598,25 @@ ValueNum ValueNumStore::VNForBitCast(ValueNum srcVN, var_types castToType)
return VNZeroForType(castToType);
}

return VNForFunc(castToType, VNF_BitCast, srcVN, VNForIntCon(castToType));
ValueNum srcExcVN;
ValueNum srcNormVN;
VNUnpackExc(srcVN, &srcNormVN, &srcExcVN);

ValueNum resultNormVN = VNForFunc(castToType, VNF_BitCast, srcNormVN, VNForIntCon(castToType));
ValueNum resultExcVN = srcExcVN;

return VNWithExc(resultNormVN, resultExcVN);
tannergooding marked this conversation as resolved.
Show resolved Hide resolved
}

// Compute the ValueNumberPair for a bitcast operation
tannergooding marked this conversation as resolved.
Show resolved Hide resolved
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 {bitCastLibVN, bitCastConVN};
tannergooding marked this conversation as resolved.
Show resolved Hide resolved
}

void Compiler::fgValueNumberHelperCallFunc(GenTreeCall* call, VNFunc vnf, ValueNumPair vnpExc)
Expand Down
4 changes: 4 additions & 0 deletions src/coreclr/jit/valuenum.h
Original file line number Diff line number Diff line change
Expand Up @@ -732,8 +732,12 @@ class ValueNumStore
bool srcIsUnsigned = false,
bool hasOverflowCheck = false);

// Compute the ValueNumber for a bitcast
ValueNum VNForBitCast(ValueNum srcVN, var_types castToType);

// Compute the ValueNumberPair for a bitcast
ValueNumPair VNPairForBitCast(ValueNumPair srcVNPair, var_types castToType);
tannergooding marked this conversation as resolved.
Show resolved Hide resolved

bool IsVNNotAField(ValueNum vn);

ValueNum VNForFieldSeq(FieldSeqNode* fieldSeq);
Expand Down
76 changes: 18 additions & 58 deletions src/libraries/System.Private.CoreLib/src/System/BitConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -767,124 +767,84 @@ public static bool ToBoolean(ReadOnlySpan<byte> value)
/// </summary>
/// <param name="value">The number to convert.</param>
/// <returns>A 64-bit signed integer whose bits are identical to <paramref name="value"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe long DoubleToInt64Bits(double value)
{
// Workaround for https:/dotnet/runtime/issues/11413
if (Sse2.X64.IsSupported)
{
Vector128<long> vec = Vector128.CreateScalarUnsafe(value).AsInt64();
return Sse2.X64.ConvertToInt64(vec);
}

return *((long*)&value);
}
[Intrinsic]
public static unsafe long DoubleToInt64Bits(double value) => *((long*)&value);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does the removal of the SSE2 path have any negative impact when using mono?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Possibly for Mono JIT. Mono LLVM correctly handles this as a bitcast already.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mono JIT doesn't support those at all. LLVM will be 100% fine without it 🙂


/// <summary>
/// Converts the specified 64-bit signed integer to a double-precision floating point number.
/// </summary>
/// <param name="value">The number to convert.</param>
/// <returns>A double-precision floating point number whose bits are identical to <paramref name="value"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe double Int64BitsToDouble(long value)
{
// Workaround for https:/dotnet/runtime/issues/11413
if (Sse2.X64.IsSupported)
{
Vector128<double> vec = Vector128.CreateScalarUnsafe(value).AsDouble();
return vec.ToScalar();
}

return *((double*)&value);
}
[Intrinsic]
public static unsafe double Int64BitsToDouble(long value) => *((double*)&value);

/// <summary>
/// Converts the specified single-precision floating point number to a 32-bit signed integer.
/// </summary>
/// <param name="value">The number to convert.</param>
/// <returns>A 32-bit signed integer whose bits are identical to <paramref name="value"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe int SingleToInt32Bits(float value)
{
// Workaround for https:/dotnet/runtime/issues/11413
if (Sse2.IsSupported)
{
Vector128<int> vec = Vector128.CreateScalarUnsafe(value).AsInt32();
return Sse2.ConvertToInt32(vec);
}

return *((int*)&value);
}
[Intrinsic]
public static unsafe int SingleToInt32Bits(float value) => *((int*)&value);

/// <summary>
/// Converts the specified 32-bit signed integer to a single-precision floating point number.
/// </summary>
/// <param name="value">The number to convert.</param>
/// <returns>A single-precision floating point number whose bits are identical to <paramref name="value"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe float Int32BitsToSingle(int value)
{
// Workaround for https:/dotnet/runtime/issues/11413
if (Sse2.IsSupported)
{
Vector128<float> vec = Vector128.CreateScalarUnsafe(value).AsSingle();
return vec.ToScalar();
}

return *((float*)&value);
}
[Intrinsic]
public static unsafe float Int32BitsToSingle(int value) => *((float*)&value);

/// <summary>
/// Converts the specified half-precision floating point number to a 16-bit signed integer.
/// </summary>
/// <param name="value">The number to convert.</param>
/// <returns>A 16-bit signed integer whose bits are identical to <paramref name="value"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe short HalfToInt16Bits(Half value) => (short)HalfToUInt16Bits(value);
public static unsafe short HalfToInt16Bits(Half value) => (short)value._value;

/// <summary>
/// Converts the specified 16-bit signed integer to a half-precision floating point number.
/// </summary>
/// <param name="value">The number to convert.</param>
/// <returns>A half-precision floating point number whose bits are identical to <paramref name="value"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe Half Int16BitsToHalf(short value) => UInt16BitsToHalf((ushort)(value));
public static unsafe Half Int16BitsToHalf(short value) => new Half((ushort)(value));

/// <summary>
/// Converts the specified double-precision floating point number to a 64-bit unsigned integer.
/// </summary>
/// <param name="value">The number to convert.</param>
/// <returns>A 64-bit unsigned integer whose bits are identical to <paramref name="value"/>.</returns>
[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);

/// <summary>
/// Converts the specified 64-bit unsigned integer to a double-precision floating point number.
/// </summary>
/// <param name="value">The number to convert.</param>
/// <returns>A double-precision floating point number whose bits are identical to <paramref name="value"/>.</returns>
[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);

/// <summary>
/// Converts the specified single-precision floating point number to a 32-bit unsigned integer.
/// </summary>
/// <param name="value">The number to convert.</param>
/// <returns>A 32-bit unsigned integer whose bits are identical to <paramref name="value"/>.</returns>
[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);

/// <summary>
/// Converts the specified 32-bit unsigned integer to a single-precision floating point number.
/// </summary>
/// <param name="value">The number to convert.</param>
/// <returns>A single-precision floating point number whose bits are identical to <paramref name="value"/>.</returns>
[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);

/// <summary>
/// Converts the specified half-precision floating point number to a 16-bit unsigned integer.
Expand Down