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

Unify Default Comparer logic #88006

Merged
merged 46 commits into from
Jul 5, 2023
Merged
Show file tree
Hide file tree
Changes from 44 commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
7f617e2
Unify Default Comparer logic
MichalPetryka Jun 24, 2023
2536992
Fix build
MichalPetryka Jun 24, 2023
5605e0e
Fix build
MichalPetryka Jun 24, 2023
72e548a
Update importercalls.cpp
MichalPetryka Jun 25, 2023
9d9fc84
Update importercalls.cpp
MichalPetryka Jun 25, 2023
f229039
Update Comparer_get_Default.cs
MichalPetryka Jun 25, 2023
70cb1c0
Update Comparer_get_Default.cs
MichalPetryka Jun 25, 2023
e095936
Update Comparer_get_Default.cs
MichalPetryka Jun 25, 2023
d591efe
Apply sugestions
MichalPetryka Jun 25, 2023
7088336
Add missed changes
MichalPetryka Jun 25, 2023
0c0c84d
Update ComparerIntrinsics.cs
MichalPetryka Jun 25, 2023
c516e7f
Update Comparer_get_Default.csproj
MichalPetryka Jun 25, 2023
d8195e6
Workaround roslyn bug
MichalPetryka Jun 26, 2023
f8249c7
Update Comparer_get_Default.cs
MichalPetryka Jun 26, 2023
4d1d88f
Update Comparer_get_Default.cs
MichalPetryka Jun 26, 2023
5ab501a
Update Comparer_get_Default.cs
MichalPetryka Jun 27, 2023
6c45b7e
Update Comparer_get_Default.cs
MichalPetryka Jun 27, 2023
40eab70
Update Comparer_get_Default.cs
MichalPetryka Jun 27, 2023
ff59591
Update Comparer_get_Default.cs
MichalPetryka Jun 27, 2023
1f2b4aa
Update Comparer_get_Default.cs
MichalPetryka Jun 27, 2023
d0627bf
Update Comparer_get_Default.cs
MichalPetryka Jun 27, 2023
ba2e5cb
Update intrinsics.c
MichalPetryka Jun 27, 2023
6bdbee2
Update transform.c
MichalPetryka Jun 27, 2023
1d14835
Update intrinsics.c
MichalPetryka Jun 28, 2023
f4009d7
Update transform.c
MichalPetryka Jun 28, 2023
6fdb5c9
Update JitHelpers.cs
MichalPetryka Jun 28, 2023
b085174
Merge branch 'dotnet:main' into comparer-type
MichalPetryka Jun 29, 2023
7659053
Update transform.c
MichalPetryka Jun 29, 2023
8ec024f
Update compiler.h
MichalPetryka Jun 30, 2023
f5a9c18
Update gentree.cpp
MichalPetryka Jun 30, 2023
c33e1de
Update importercalls.cpp
MichalPetryka Jun 30, 2023
588d884
Update importercalls.cpp
MichalPetryka Jun 30, 2023
a64df29
Update importercalls.cpp
MichalPetryka Jun 30, 2023
fa682a2
Move Canon checking to VM
MichalPetryka Jun 30, 2023
859a25d
Check in EqualityComparer too
MichalPetryka Jun 30, 2023
45ec76d
Update Comparer_get_Default.cs
MichalPetryka Jun 30, 2023
1c79052
Update src/coreclr/jit/importercalls.cpp
MichalPetryka Jun 30, 2023
d0ad2dd
Update Comparer_get_Default.cs
MichalPetryka Jun 30, 2023
8471b21
Update Comparer_get_Default.cs
MichalPetryka Jun 30, 2023
1999819
Update importercalls.cpp
MichalPetryka Jun 30, 2023
0772c85
Merge branch 'dotnet:main' into comparer-type
MichalPetryka Jun 30, 2023
35ee0a2
Update Comparer_get_Default.cs
MichalPetryka Jun 30, 2023
7c902b4
Update Comparer_get_Default.cs
MichalPetryka Jun 30, 2023
fc21b4f
Update jitinterface.cpp
MichalPetryka Jul 3, 2023
fff6180
Update jitinterface.cpp
MichalPetryka Jul 4, 2023
1b1c1e7
Update Comparer_get_Default.cs
MichalPetryka Jul 5, 2023
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 @@ -46,44 +46,12 @@ internal static object CreateDefaultComparer(Type type)
// The comparer for enums is specialized to avoid boxing.
else if (type.IsEnum)
{
result = TryCreateEnumComparer(runtimeType);
result = CreateInstanceForAnotherGenericParameter((RuntimeType)typeof(EnumComparer<>), runtimeType);
}

return result ?? CreateInstanceForAnotherGenericParameter((RuntimeType)typeof(ObjectComparer<object>), runtimeType);
}

/// <summary>
/// Creates the default <see cref="Comparer{T}"/> for an enum type.
/// </summary>
/// <param name="enumType">The enum type to create the default comparer for.</param>
private static object? TryCreateEnumComparer(RuntimeType enumType)
{
Debug.Assert(enumType != null);
Debug.Assert(enumType.IsEnum);

// Explicitly call Enum.GetUnderlyingType here. Although GetTypeCode
// ends up doing this anyway, we end up avoiding an unnecessary P/Invoke
// and virtual method call.
TypeCode underlyingTypeCode = Type.GetTypeCode(Enum.GetUnderlyingType(enumType));

// Depending on the enum type, we need to special case the comparers so that we avoid boxing.
// Specialize differently for signed/unsigned types so we avoid problems with large numbers.
switch (underlyingTypeCode)
{
case TypeCode.SByte:
case TypeCode.Int16:
case TypeCode.Int32:
case TypeCode.Byte:
case TypeCode.UInt16:
case TypeCode.UInt32:
case TypeCode.Int64:
case TypeCode.UInt64:
return CreateInstanceForAnotherGenericParameter((RuntimeType)typeof(EnumComparer<>), enumType);
}

return null;
}

/// <summary>
/// Creates the default <see cref="EqualityComparer{T}"/>.
/// </summary>
Expand All @@ -98,15 +66,9 @@ internal static object CreateDefaultEqualityComparer(Type type)
object? result = null;
var runtimeType = (RuntimeType)type;

if (type == typeof(byte))
{
// Specialize for byte so Array.IndexOf is faster.
result = new ByteEqualityComparer();
}
else if (type == typeof(string))
if (type == typeof(string))
{
// Specialize for string, as EqualityComparer<string>.Default is on the startup path
MichalPetryka marked this conversation as resolved.
Show resolved Hide resolved
result = new GenericEqualityComparer<string>();
jkotas marked this conversation as resolved.
Show resolved Hide resolved
return new GenericEqualityComparer<string>();
}
else if (type.IsAssignableTo(typeof(IEquatable<>).MakeGenericType(type)))
{
Expand All @@ -122,41 +84,10 @@ internal static object CreateDefaultEqualityComparer(Type type)
else if (type.IsEnum)
{
// The equality comparer for enums is specialized to avoid boxing.
result = TryCreateEnumEqualityComparer(runtimeType);
result = CreateInstanceForAnotherGenericParameter((RuntimeType)typeof(EnumEqualityComparer<>), runtimeType);
}

return result ?? CreateInstanceForAnotherGenericParameter((RuntimeType)typeof(ObjectEqualityComparer<object>), runtimeType);
}

/// <summary>
/// Creates the default <see cref="EqualityComparer{T}"/> for an enum type.
/// </summary>
/// <param name="enumType">The enum type to create the default equality comparer for.</param>
private static object? TryCreateEnumEqualityComparer(RuntimeType enumType)
{
Debug.Assert(enumType != null);
Debug.Assert(enumType.IsEnum);

// See the METHOD__JIT_HELPERS__UNSAFE_ENUM_CAST and METHOD__JIT_HELPERS__UNSAFE_ENUM_CAST_LONG cases in getILIntrinsicImplementation
// for how we cast the enum types to integral values in the comparer without boxing.

TypeCode underlyingTypeCode = Type.GetTypeCode(Enum.GetUnderlyingType(enumType));

// Depending on the enum type, we need to special case the comparers so that we avoid boxing.
switch (underlyingTypeCode)
MichalPetryka marked this conversation as resolved.
Show resolved Hide resolved
{
case TypeCode.Int32:
case TypeCode.UInt32:
case TypeCode.SByte:
case TypeCode.Byte:
case TypeCode.Int16:
case TypeCode.Int64:
case TypeCode.UInt64:
case TypeCode.UInt16:
return CreateInstanceForAnotherGenericParameter((RuntimeType)typeof(EnumEqualityComparer<>), enumType);
}

return null;
}
}
}
56 changes: 20 additions & 36 deletions src/coreclr/jit/importercalls.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7553,57 +7553,41 @@ CORINFO_CLASS_HANDLE Compiler::impGetSpecialIntrinsicExactReturnType(GenTreeCall
CORINFO_SIG_INFO sig;
info.compCompHnd->getMethodSig(methodHnd, &sig);
assert(sig.sigInst.classInstCount == 1);

CORINFO_CLASS_HANDLE typeHnd = sig.sigInst.classInst[0];
assert(typeHnd != nullptr);

// Lookup can incorrect when we have __Canon as it won't appear
// to implement any interface types.
//
// And if we do not have a final type, devirt & inlining is
// unlikely to result in much simplification.
//
// We can use CORINFO_FLG_FINAL to screen out both of these cases.
const DWORD typeAttribs = info.compCompHnd->getClassAttribs(typeHnd);
bool isFinalType = ((typeAttribs & CORINFO_FLG_FINAL) != 0);

if (!isFinalType)
CallArg* instParam = call->gtArgs.FindWellKnownArg(WellKnownArg::InstParam);
if (instParam != nullptr)
{
CallArg* instParam = call->gtArgs.FindWellKnownArg(WellKnownArg::InstParam);
if (instParam != nullptr)
assert(instParam->GetNext() == nullptr);
CORINFO_CLASS_HANDLE hClass = gtGetHelperArgClassHandle(instParam->GetNode());
if (hClass != NO_CLASS_HANDLE)
{
assert(instParam->GetNext() == nullptr);
CORINFO_CLASS_HANDLE hClass = gtGetHelperArgClassHandle(instParam->GetNode());
if (hClass != NO_CLASS_HANDLE)
{
hClass = getTypeInstantiationArgument(hClass, 0);
if ((info.compCompHnd->getClassAttribs(hClass) & CORINFO_FLG_FINAL) != 0)
{
typeHnd = hClass;
isFinalType = true;
}
}
typeHnd = getTypeInstantiationArgument(hClass, 0);
}
}

if (isFinalType)
if (ni == NI_System_Collections_Generic_EqualityComparer_get_Default)
{
result = info.compCompHnd->getDefaultEqualityComparerClass(typeHnd);
}
else
{
assert(ni == NI_System_Collections_Generic_Comparer_get_Default);
result = info.compCompHnd->getDefaultComparerClass(typeHnd);
}

if (result != NO_CLASS_HANDLE)
{
if (ni == NI_System_Collections_Generic_EqualityComparer_get_Default)
{
result = info.compCompHnd->getDefaultEqualityComparerClass(typeHnd);
}
else
{
assert(ni == NI_System_Collections_Generic_Comparer_get_Default);
result = info.compCompHnd->getDefaultComparerClass(typeHnd);
}
JITDUMP("Special intrinsic for type %s: return type is %s\n", eeGetClassName(typeHnd),
result != nullptr ? eeGetClassName(result) : "unknown");
}
else
{
JITDUMP("Special intrinsic for type %s: type not final, so deferring opt\n", eeGetClassName(typeHnd));
JITDUMP("Special intrinsic for type %s: type undetermined, so deferring opt\n",
eeGetClassName(typeHnd));
}

break;
}

Expand Down
1 change: 0 additions & 1 deletion src/coreclr/vm/corelib.h
Original file line number Diff line number Diff line change
Expand Up @@ -1155,7 +1155,6 @@ DEFINE_METHOD(UTF8BUFFERMARSHALER, CONVERT_TO_MANAGED, ConvertToManaged, NoSig)

// Classes referenced in EqualityComparer<T>.Default optimization

DEFINE_CLASS(BYTE_EQUALITYCOMPARER, CollectionsGeneric, ByteEqualityComparer)
DEFINE_CLASS(ENUM_EQUALITYCOMPARER, CollectionsGeneric, EnumEqualityComparer`1)
DEFINE_CLASS(NULLABLE_EQUALITYCOMPARER, CollectionsGeneric, NullableEqualityComparer`1)
DEFINE_CLASS(GENERIC_EQUALITYCOMPARER, CollectionsGeneric, GenericEqualityComparer`1)
Expand Down
78 changes: 14 additions & 64 deletions src/coreclr/vm/jitinterface.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8803,44 +8803,20 @@ CORINFO_CLASS_HANDLE CEEInfo::getDefaultComparerClassHelper(CORINFO_CLASS_HANDLE
if (Nullable::IsNullableType(elemTypeHnd))
{
Instantiation nullableInst = elemTypeHnd.AsMethodTable()->GetInstantiation();
TypeHandle iequatable = TypeHandle(CoreLibBinder::GetClass(CLASS__IEQUATABLEGENERIC)).Instantiate(nullableInst);
if (nullableInst[0].CanCastTo(iequatable))
{
TypeHandle resultTh = ((TypeHandle)CoreLibBinder::GetClass(CLASS__NULLABLE_COMPARER)).Instantiate(nullableInst);
return CORINFO_CLASS_HANDLE(resultTh.GetMethodTable());
}
TypeHandle resultTh = ((TypeHandle)CoreLibBinder::GetClass(CLASS__NULLABLE_COMPARER)).Instantiate(nullableInst);
return CORINFO_CLASS_HANDLE(resultTh.GetMethodTable());
}

// We need to special case the Enum comparers based on their underlying type to avoid boxing
if (elemTypeHnd.IsEnum())
{
MethodTable* targetClass = NULL;
CorElementType normType = elemTypeHnd.GetVerifierCorElementType();

switch(normType)
{
case ELEMENT_TYPE_I1:
case ELEMENT_TYPE_I2:
case ELEMENT_TYPE_U1:
case ELEMENT_TYPE_U2:
case ELEMENT_TYPE_I4:
case ELEMENT_TYPE_U4:
case ELEMENT_TYPE_I8:
case ELEMENT_TYPE_U8:
{
targetClass = CoreLibBinder::GetClass(CLASS__ENUM_COMPARER);
break;
}

default:
break;
}
TypeHandle resultTh = ((TypeHandle)CoreLibBinder::GetClass(CLASS__ENUM_COMPARER)).Instantiate(inst);
return CORINFO_CLASS_HANDLE(resultTh.GetMethodTable());
jkotas marked this conversation as resolved.
Show resolved Hide resolved
}

if (targetClass != NULL)
{
TypeHandle resultTh = ((TypeHandle)targetClass->GetCanonicalMethodTable()).Instantiate(inst);
return CORINFO_CLASS_HANDLE(resultTh.GetMethodTable());
}
if (elemTypeHnd.IsCanonicalSubtype())
Copy link
Member

@jkotas jkotas Jul 4, 2023

Choose a reason for hiding this comment

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

The CanCastTo(IEquatable) case needs to be after this check, like I have suggested in my comment.

Or the IsCanonicalSubtype check needs to be the very first thing in the method - we would give up on more cases that way. (It would mirror what happens today.)

Or the CanCastTo(IEquatable) check needs to be implemented against generic type definition for the IsCanonicalSubtype case. This would be the most precise option, but there would be still some ambiguous cases where we would have to give up.

Here is example of a bug that would be hit by the code as written:

using System;
using System.Threading;
using System.Collections;
using System.Collections.Generic;
using System.Runtime.CompilerServices;

for (int i = 0; i < 100; i++)
{
   Internal.Console.WriteLine(Test<string, object>().ToString());
   Thread.Sleep(1);
}

[MethodImpl(MethodImplOptions.NoInlining)]
static bool Test<T,U>()
{
    return EqualityComparer<G<T,U>>.Default.Equals(default, default);
}

struct G<T,U> : IEquatable<G<U,T>>
{
   public bool Equals(G<U,T> x) => false;
}

It will start printing true correctly, and then flips to printing false as the incorrect Tier1 optimization kicks in.

Copy link
Contributor Author

@MichalPetryka MichalPetryka Jul 4, 2023

Choose a reason for hiding this comment

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

(It would mirror what happens today.)

It wouldn't, it'd then block Struct<_Canon> too, which you were concerned about before.
The issue right now is that this seems to still be devirtualizing __Canon as ObjectEqualityComparer for some reason (see those diffs). I have no idea what's going on here, could WellKnownArg be returning some weird handle here?

Copy link
Member

Choose a reason for hiding this comment

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

Right, it won't mirror the bugs. Notice that the bug demonstrated by my G<T,U> repro has been shipping for a while (just tried it on .NET 7).

Struct<_Canon> needs the third option (use generic type definition to check for IEquatable implementation). Otherwise, it is better to block it rather than to have functional bugs that manifest as observable behavior differences between Tier0 and Tier1 JIT.

Copy link
Member

Choose a reason for hiding this comment

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

see those diffs

This links to my comment. Did you meant to link to some diffs somewhere?

Copy link
Contributor Author

@MichalPetryka MichalPetryka Jul 4, 2023

Choose a reason for hiding this comment

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

This links to my comment. Did you meant to link to some diffs somewhere?

Yeah, seems like Copy link on that GitHub message didn't work so I still had a link to your comment in my clipboard instead. Fixed that link.

Copy link
Member

Choose a reason for hiding this comment

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

There diffs look odd. Are you able to reproduce the behavior locally? It can be that the bot picked up stale bits from somewhere.

Copy link
Member

Choose a reason for hiding this comment

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

The new diffs look good to me, but I do not think the difference can be explained by the one commit that you have pushed.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The new diffs look good to me, but I do not think the difference can be explained by the one commit that you have pushed.

The bot seems to be having some issues with picking up VM changes, after a fix CoreLib changes look good but frameworks ones still have the weird diff, not sure if it's still an issue with the bot.

{
return NULL;
}

// Default case
Expand Down Expand Up @@ -8881,12 +8857,6 @@ CORINFO_CLASS_HANDLE CEEInfo::getDefaultEqualityComparerClassHelper(CORINFO_CLAS
// And in compile.cpp's SpecializeEqualityComparer
TypeHandle elemTypeHnd(elemType);

// Special case for byte
if (elemTypeHnd.AsMethodTable()->HasSameTypeDefAs(CoreLibBinder::GetClass(CLASS__ELEMENT_TYPE_U1)))
{
return CORINFO_CLASS_HANDLE(CoreLibBinder::GetClass(CLASS__BYTE_EQUALITYCOMPARER));
}

// Mirrors the logic in BCL's CompareHelpers.CreateDefaultComparer
// And in compile.cpp's SpecializeComparer
//
Expand Down Expand Up @@ -8914,33 +8884,13 @@ CORINFO_CLASS_HANDLE CEEInfo::getDefaultEqualityComparerClassHelper(CORINFO_CLAS
// to avoid boxing and call the correct versions of GetHashCode.
if (elemTypeHnd.IsEnum())
{
MethodTable* targetClass = NULL;
CorElementType normType = elemTypeHnd.GetVerifierCorElementType();

switch(normType)
{
case ELEMENT_TYPE_I1:
case ELEMENT_TYPE_I2:
case ELEMENT_TYPE_U1:
case ELEMENT_TYPE_U2:
case ELEMENT_TYPE_I4:
case ELEMENT_TYPE_U4:
case ELEMENT_TYPE_I8:
case ELEMENT_TYPE_U8:
{
targetClass = CoreLibBinder::GetClass(CLASS__ENUM_EQUALITYCOMPARER);
break;
}

default:
break;
}
TypeHandle resultTh = ((TypeHandle)CoreLibBinder::GetClass(CLASS__ENUM_EQUALITYCOMPARER)).Instantiate(inst);
return CORINFO_CLASS_HANDLE(resultTh.GetMethodTable());
}

if (targetClass != NULL)
{
TypeHandle resultTh = ((TypeHandle)targetClass->GetCanonicalMethodTable()).Instantiate(inst);
return CORINFO_CLASS_HANDLE(resultTh.GetMethodTable());
}
if (elemTypeHnd.IsCanonicalSubtype())
{
return NULL;
}

// Default case
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,7 @@ private static EqualityComparer<T> CreateComparer()
// IN mini_handle_call_res_devirt
/////////////////////////////////////////////////

if (t == typeof(byte))
{
return (EqualityComparer<T>)(object)(new ByteEqualityComparer());
}
else if (t == typeof(string))
if (t == typeof(string))
{
// Specialize for string, as EqualityComparer<string>.Default is on the startup path
return (EqualityComparer<T>)(object)(new GenericEqualityComparer<string>());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,11 @@ namespace System.Runtime.CompilerServices
{
internal static class JitHelpers
{
#pragma warning disable IDE0060
[Intrinsic]
public static bool EnumEquals<T>(T x, T y) where T : struct, Enum => throw new NotImplementedException();
public static bool EnumEquals<T>(T x, T y) where T : struct, Enum => x.Equals(y);

[Intrinsic]
public static int EnumCompareTo<T>(T x, T y) where T : struct, Enum => throw new NotImplementedException();
#pragma warning restore IDE0060
public static int EnumCompareTo<T>(T x, T y) where T : struct, Enum => x.CompareTo(y);

[Intrinsic]
internal static void DisableInline () => throw new NotImplementedException();
Expand Down
5 changes: 4 additions & 1 deletion src/mono/mono/mini/interp/transform.c
Original file line number Diff line number Diff line change
Expand Up @@ -2536,7 +2536,10 @@ interp_handle_intrinsics (TransformData *td, MonoMethod *target_method, MonoClas
MonoType *t = ctx->method_inst->type_argv [0];
t = mini_get_underlying_type (t);

gboolean is_i8 = (t->type == MONO_TYPE_I8 || t->type == MONO_TYPE_U8);
if (t->type == MONO_TYPE_R4 || t->type == MONO_TYPE_R8)
return FALSE;

gboolean is_i8 = (t->type == MONO_TYPE_I8 || t->type == MONO_TYPE_U8 || (TARGET_SIZEOF_VOID_P == 8 && (t->type == MONO_TYPE_I || t->type == MONO_TYPE_U)));
gboolean is_unsigned = (t->type == MONO_TYPE_U1 || t->type == MONO_TYPE_U2 || t->type == MONO_TYPE_U4 || t->type == MONO_TYPE_U8 || t->type == MONO_TYPE_U);

gboolean is_compareto = strcmp (tm, "EnumCompareTo") == 0;
Expand Down
4 changes: 2 additions & 2 deletions src/mono/mono/mini/intrinsics.c
Original file line number Diff line number Diff line change
Expand Up @@ -672,10 +672,10 @@ emit_jit_helpers_intrinsics (MonoCompile *cfg, MonoMethod *cmethod, MonoMethodSi

t = ctx->method_inst->type_argv [0];
t = mini_get_underlying_type (t);
if (mini_is_gsharedvt_variable_type (t))
if (mini_is_gsharedvt_variable_type (t) || t->type == MONO_TYPE_R4 || t->type == MONO_TYPE_R8)
return NULL;

gboolean is_i8 = (t->type == MONO_TYPE_I8 || t->type == MONO_TYPE_U8);
gboolean is_i8 = (t->type == MONO_TYPE_I8 || t->type == MONO_TYPE_U8 || (TARGET_SIZEOF_VOID_P == 8 && (t->type == MONO_TYPE_I || t->type == MONO_TYPE_U)));
gboolean is_unsigned = (t->type == MONO_TYPE_U1 || t->type == MONO_TYPE_U2 || t->type == MONO_TYPE_U4 || t->type == MONO_TYPE_U8 || t->type == MONO_TYPE_U);
int cmp_op, ceq_op, cgt_op, clt_op;

Expand Down
2 changes: 1 addition & 1 deletion src/mono/mono/mini/mini.c
Original file line number Diff line number Diff line change
Expand Up @@ -4301,7 +4301,7 @@ mini_handle_call_res_devirt (MonoMethod *cmethod)

// EqualityComparer<T>.Default returns specific types depending on T
// FIXME: Special case more types: byte, string, nullable, enum ?
if (mono_class_is_assignable_from_internal (inst, mono_class_from_mono_type_internal (param_type)) && param_type->type != MONO_TYPE_U1 && param_type->type != MONO_TYPE_STRING) {
if (mono_class_is_assignable_from_internal (inst, mono_class_from_mono_type_internal (param_type)) && param_type->type != MONO_TYPE_STRING) {
MonoClass *gcomparer_inst;

memset (&ctx, 0, sizeof (ctx));
Expand Down
Loading