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

Optimize 'ConvertOrWidenPrimitivesEnumsAndPointersIfPossible' #101858

Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -134,87 +134,341 @@ private static Exception ConvertOrWidenPrimitivesEnumsAndPointersIfPossible(obje
return CreateChangeTypeArgumentException(srcEEType, dstEEType);
}

EETypeElementType srcElementType = srcEEType->ElementType;

void* rawDstValue = null;

Unsafe.SkipInit(out char charValue);
Unsafe.SkipInit(out sbyte sbyteValue);
Unsafe.SkipInit(out byte byteValue);
Unsafe.SkipInit(out short shortValue);
Unsafe.SkipInit(out ushort ushortValue);
Unsafe.SkipInit(out int intValue);
Unsafe.SkipInit(out uint uintValue);
Unsafe.SkipInit(out long longValue);
Unsafe.SkipInit(out ulong ulongValue);
Unsafe.SkipInit(out float floatValue);
Unsafe.SkipInit(out double doubleValue);

Unsafe.SkipInit(out dstObject);

Sergio0694 marked this conversation as resolved.
Show resolved Hide resolved
// This switch supports the conversions present in the table below, in 'CanPrimitiveWiden'
switch (dstElementType)
{
case EETypeElementType.Boolean:
dstObject = Convert.ToBoolean(srcObject);

// Can only be bool here
dstObject = srcObject;
break;

case EETypeElementType.Char:
char charValue = Convert.ToChar(srcObject);
dstObject = dstEEType->IsEnum ? Enum.ToObject(dstEEType, charValue) : charValue;
switch (srcElementType)
{
case EETypeElementType.Char:
charValue = RuntimeHelpers.FastUnbox<char>(srcObject);
Sergio0694 marked this conversation as resolved.
Show resolved Hide resolved
break;
case EETypeElementType.Byte:
charValue = (char)RuntimeHelpers.FastUnbox<byte>(srcObject);
break;
case EETypeElementType.UInt16:
charValue = (char)RuntimeHelpers.FastUnbox<ushort>(srcObject);
break;
default:
goto Failure;
}
rawDstValue = &charValue;
break;

case EETypeElementType.SByte:
sbyte sbyteValue = Convert.ToSByte(srcObject);
dstObject = dstEEType->IsEnum ? Enum.ToObject(dstEEType, sbyteValue) : sbyteValue;
break;

case EETypeElementType.Int16:
short shortValue = Convert.ToInt16(srcObject);
dstObject = dstEEType->IsEnum ? Enum.ToObject(dstEEType, shortValue) : shortValue;
// Can only be sbyte here
Copy link
Member

Choose a reason for hiding this comment

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

Is this correct? Can this be reboxing enum to primitive or vice versa? I think the exact matches are handled earlier.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

What do you mean? My understanding was that regardless of whether either value was an enum here, we're just copying the raw primitive value to that local and then RuntimeHelpers.Box will take care of both scenarios at the end, either just boxing the primitive or boxing it as the target enum type. Is that not correct? 🤔

Copy link
Member

Choose a reason for hiding this comment

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

This comment was meant to be on the bool case. The bool-backed enums are partially supported. I am not sure whether we can get here with bool backed enum, but the current code handles the bool backed enums so it would keep it that way.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Oh interesting. I thought we had removed bool-backed enum support in .NET 8. Anyway, fixed in cdccf69. It also allows us to remove that check on rawDstValue not being null, so that's also nice 😄

sbyteValue = RuntimeHelpers.FastUnbox<sbyte>(srcObject);
rawDstValue = &sbyteValue;
break;

case EETypeElementType.Int32:
int intValue = Convert.ToInt32(srcObject);
dstObject = dstEEType->IsEnum ? Enum.ToObject(dstEEType, intValue) : intValue;
break;
case EETypeElementType.Byte:
jkotas marked this conversation as resolved.
Show resolved Hide resolved

case EETypeElementType.Int64:
long longValue = Convert.ToInt64(srcObject);
dstObject = dstEEType->IsEnum ? Enum.ToObject(dstEEType, longValue) : longValue;
// Can only be byte here
byteValue = RuntimeHelpers.FastUnbox<byte>(srcObject);
rawDstValue = &byteValue;
break;

case EETypeElementType.Byte:
byte byteValue = Convert.ToByte(srcObject);
dstObject = dstEEType->IsEnum ? Enum.ToObject(dstEEType, byteValue) : byteValue;
case EETypeElementType.Int16:
switch (srcElementType)
{
case EETypeElementType.SByte:
shortValue = (short)RuntimeHelpers.FastUnbox<sbyte>(srcObject);
break;
case EETypeElementType.Byte:
shortValue = (short)RuntimeHelpers.FastUnbox<byte>(srcObject);
break;
case EETypeElementType.Int16:
shortValue = RuntimeHelpers.FastUnbox<short>(srcObject);
break;
default:
goto Failure;
}
rawDstValue = &shortValue;
break;

case EETypeElementType.UInt16:
Sergio0694 marked this conversation as resolved.
Show resolved Hide resolved
ushort ushortValue = Convert.ToUInt16(srcObject);
dstObject = dstEEType->IsEnum ? Enum.ToObject(dstEEType, ushortValue) : ushortValue;
switch (srcElementType)
{
case EETypeElementType.Char:
ushortValue = (ushort)RuntimeHelpers.FastUnbox<char>(srcObject);
break;
case EETypeElementType.Byte:
ushortValue = (ushort)RuntimeHelpers.FastUnbox<byte>(srcObject);
break;
case EETypeElementType.UInt16:
Sergio0694 marked this conversation as resolved.
Show resolved Hide resolved
ushortValue = RuntimeHelpers.FastUnbox<ushort>(srcObject);
break;
default:
goto Failure;
}
rawDstValue = &ushortValue;
break;

case EETypeElementType.UInt32:
uint uintValue = Convert.ToUInt32(srcObject);
dstObject = dstEEType->IsEnum ? Enum.ToObject(dstEEType, uintValue) : uintValue;
case EETypeElementType.Int32:
switch (srcElementType)
{
case EETypeElementType.Char:
intValue = (int)RuntimeHelpers.FastUnbox<char>(srcObject);
break;
case EETypeElementType.SByte:
intValue = (int)RuntimeHelpers.FastUnbox<sbyte>(srcObject);
break;
case EETypeElementType.Byte:
intValue = (int)RuntimeHelpers.FastUnbox<byte>(srcObject);
break;
case EETypeElementType.Int16:
intValue = (int)RuntimeHelpers.FastUnbox<short>(srcObject);
break;
case EETypeElementType.UInt16:
intValue = (int)RuntimeHelpers.FastUnbox<ushort>(srcObject);
break;
case EETypeElementType.Int32:
intValue = RuntimeHelpers.FastUnbox<int>(srcObject);
break;
default:
goto Failure;
}
rawDstValue = &intValue;
break;

case EETypeElementType.UInt64:
ulong ulongValue = Convert.ToUInt64(srcObject);
dstObject = dstEEType->IsEnum ? Enum.ToObject(dstEEType, (long)ulongValue) : ulongValue;
case EETypeElementType.UInt32:
switch (srcElementType)
{
case EETypeElementType.Char:
uintValue = (uint)RuntimeHelpers.FastUnbox<char>(srcObject);
break;
case EETypeElementType.Byte:
uintValue = (uint)RuntimeHelpers.FastUnbox<byte>(srcObject);
break;
case EETypeElementType.UInt16:
uintValue = (uint)RuntimeHelpers.FastUnbox<ushort>(srcObject);
break;
case EETypeElementType.UInt32:
uintValue = RuntimeHelpers.FastUnbox<uint>(srcObject);
break;
default:
goto Failure;
}
rawDstValue = &uintValue;
break;

case EETypeElementType.Single:
if (new EETypePtr(srcEEType).CorElementType == CorElementType.ELEMENT_TYPE_CHAR)
case EETypeElementType.Int64:
switch (srcElementType)
{
dstObject = (float)(char)srcObject;
case EETypeElementType.Char:
longValue = (long)RuntimeHelpers.FastUnbox<char>(srcObject);
break;
case EETypeElementType.SByte:
longValue = (long)RuntimeHelpers.FastUnbox<sbyte>(srcObject);
break;
case EETypeElementType.Byte:
longValue = (long)RuntimeHelpers.FastUnbox<byte>(srcObject);
break;
case EETypeElementType.Int16:
longValue = (long)RuntimeHelpers.FastUnbox<short>(srcObject);
break;
case EETypeElementType.UInt16:
longValue = (long)RuntimeHelpers.FastUnbox<ushort>(srcObject);
break;
case EETypeElementType.Int32:
longValue = (long)RuntimeHelpers.FastUnbox<int>(srcObject);
break;
case EETypeElementType.UInt32:
longValue = (long)RuntimeHelpers.FastUnbox<uint>(srcObject);
break;
case EETypeElementType.Int64:
longValue = RuntimeHelpers.FastUnbox<long>(srcObject);
break;
default:
goto Failure;
}
else
rawDstValue = &longValue;
break;

case EETypeElementType.UInt64:
switch (srcElementType)
{
dstObject = Convert.ToSingle(srcObject);
case EETypeElementType.Char:
ulongValue = (ulong)RuntimeHelpers.FastUnbox<char>(srcObject);
break;
case EETypeElementType.Byte:
ulongValue = (ulong)RuntimeHelpers.FastUnbox<byte>(srcObject);
break;
case EETypeElementType.UInt16:
ulongValue = (ulong)RuntimeHelpers.FastUnbox<ushort>(srcObject);
break;
case EETypeElementType.UInt32:
ulongValue = (ulong)RuntimeHelpers.FastUnbox<uint>(srcObject);
break;
case EETypeElementType.UInt64:
ulongValue = RuntimeHelpers.FastUnbox<ulong>(srcObject);
break;
default:
goto Failure;
}
rawDstValue = &ulongValue;
break;

case EETypeElementType.Double:
if (new EETypePtr(srcEEType).CorElementType == CorElementType.ELEMENT_TYPE_CHAR)
case EETypeElementType.Single:
switch (srcElementType)
{
dstObject = (double)(char)srcObject;
case EETypeElementType.Char:
floatValue = (float)RuntimeHelpers.FastUnbox<char>(srcObject);
break;
case EETypeElementType.SByte:
floatValue = (float)RuntimeHelpers.FastUnbox<sbyte>(srcObject);
break;
case EETypeElementType.Byte:
floatValue = (float)RuntimeHelpers.FastUnbox<byte>(srcObject);
break;
case EETypeElementType.Int16:
floatValue = (float)RuntimeHelpers.FastUnbox<short>(srcObject);
break;
case EETypeElementType.UInt16:
floatValue = (float)RuntimeHelpers.FastUnbox<ushort>(srcObject);
break;
case EETypeElementType.Int32:
floatValue = (float)RuntimeHelpers.FastUnbox<int>(srcObject);
break;
case EETypeElementType.UInt32:
Sergio0694 marked this conversation as resolved.
Show resolved Hide resolved
floatValue = (float)RuntimeHelpers.FastUnbox<uint>(srcObject);
break;
case EETypeElementType.Int64:
floatValue = (float)RuntimeHelpers.FastUnbox<long>(srcObject);
break;
case EETypeElementType.UInt64:
floatValue = (float)RuntimeHelpers.FastUnbox<ulong>(srcObject);
break;
case EETypeElementType.Single:
floatValue = RuntimeHelpers.FastUnbox<float>(srcObject);
break;
default:
goto Failure;
}
else
rawDstValue = &floatValue;
break;

case EETypeElementType.Double:
switch (srcElementType)
{
dstObject = Convert.ToDouble(srcObject);
case EETypeElementType.Char:
doubleValue = (double)RuntimeHelpers.FastUnbox<char>(srcObject);
break;
case EETypeElementType.SByte:
doubleValue = (double)RuntimeHelpers.FastUnbox<sbyte>(srcObject);
break;
case EETypeElementType.Byte:
doubleValue = (double)RuntimeHelpers.FastUnbox<byte>(srcObject);
break;
case EETypeElementType.Int16:
doubleValue = (double)RuntimeHelpers.FastUnbox<short>(srcObject);
break;
case EETypeElementType.UInt16:
doubleValue = (double)RuntimeHelpers.FastUnbox<ushort>(srcObject);
break;
case EETypeElementType.Int32:
doubleValue = (double)RuntimeHelpers.FastUnbox<int>(srcObject);
break;
case EETypeElementType.UInt32:
doubleValue = (double)RuntimeHelpers.FastUnbox<uint>(srcObject);
break;
case EETypeElementType.Int64:
doubleValue = (double)RuntimeHelpers.FastUnbox<long>(srcObject);
break;
case EETypeElementType.UInt64:
doubleValue = (double)RuntimeHelpers.FastUnbox<ulong>(srcObject);
break;
case EETypeElementType.Single:
doubleValue = (double)RuntimeHelpers.FastUnbox<float>(srcObject);
break;
case EETypeElementType.Double:
doubleValue = RuntimeHelpers.FastUnbox<double>(srcObject);
break;
default:
goto Failure;
}
rawDstValue = &doubleValue;
break;

default:
Debug.Fail("Unexpected CorElementType: " + dstElementType + ": Not a valid widening target.");
dstObject = null;
return CreateChangeTypeException(srcEEType, dstEEType, semantics);
goto Failure;
}

if (rawDstValue != null)
{
if (dstEEType->IsEnum)
{
long rawEnumValue;
switch (dstElementType)
{
case EETypeElementType.Char:
rawEnumValue = (long)charValue;
break;
case EETypeElementType.SByte:
rawEnumValue = (long)sbyteValue;
break;
case EETypeElementType.Byte:
rawEnumValue = (long)byteValue;
break;
case EETypeElementType.Int16:
rawEnumValue = (long)shortValue;
break;
case EETypeElementType.UInt16:
rawEnumValue = (long)ushortValue;
break;
case EETypeElementType.Int32:
rawEnumValue = (long)intValue;
break;
case EETypeElementType.UInt32:
rawEnumValue = (long)uintValue;
break;
case EETypeElementType.Int64:
rawEnumValue = longValue;
break;
default:
rawEnumValue = (long)ulongValue;
break;
}
dstObject = Enum.ToObject(dstEEType, rawEnumValue);
}
else
{
dstObject = RuntimeHelpers.Box(ref *(byte*)&rawDstValue, new RuntimeTypeHandle(dstEEType));
Sergio0694 marked this conversation as resolved.
Show resolved Hide resolved
}
}

Debug.Assert(dstObject.GetMethodTable() == dstEEType);
jkotas marked this conversation as resolved.
Show resolved Hide resolved
return null;

Failure:
Debug.Fail("Unexpected CorElementType: " + dstElementType + ": Not a valid widening target.");
dstObject = null;
return null; // This code can never be reached
}

private static bool CanPrimitiveWiden(EETypeElementType destType, EETypeElementType srcType)
Copy link
Member

Choose a reason for hiding this comment

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

Delete - no longer used?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Removed in 54092d4.

Copy link
Member

Choose a reason for hiding this comment

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

Can we delete the CanPrimitiveWiden method and create the exception at the end? I do not think that we care about how fast the exception gets created.

Copy link
Contributor Author

@Sergio0694 Sergio0694 May 4, 2024

Choose a reason for hiding this comment

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

I deleted it in 95ea639🙂
Will leave this open, feel free to resolve if that change matches what you had in mind.

Expand All @@ -232,7 +486,7 @@ private static bool CanPrimitiveWiden(EETypeElementType destType, EETypeElementT
0xC540, // Int16 (W = I2, I4, I8, R4, R8)
0xCF88, // UInt16 (W = U2, CHAR, I4, U4, I8, U8, R4, R8)
0xC500, // Int32 (W = I4, I8, R4, R8)
0xCE00, // UInt32 (W = U4, I8, R4, R8)
0xCE0B, // UInt32 (W = U4, I8, U8, R4, R8)
0xC400, // Int64 (W = I8, R4, R8)
0xC800, // UInt64 (W = U8, R4, R8)
0x0000, // IntPtr
Expand Down
Loading
Loading