-
Notifications
You must be signed in to change notification settings - Fork 4.9k
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
Unify Default Comparer logic #88006
Conversation
Tagging subscribers to this area: @JulieLeeMSFT, @jakobbotsch Issue DetailsUnifies (Equality)Comparer.Default logic so that it matches between CoreCLR, NativeAOT, Mono in runtime and intrinsic implementations. Also enables devirt for non final types in CoreCLR and NativeAOT. Required for #87579.
|
It should fix #87391 |
src/coreclr/System.Private.CoreLib/src/System/Collections/Generic/ComparerHelpers.cs
Show resolved
Hide resolved
src/coreclr/tools/Common/TypeSystem/IL/Stubs/ComparerIntrinsics.cs
Outdated
Show resolved
Hide resolved
...lr/nativeaot/System.Private.CoreLib/src/Internal/IntrinsicSupport/EqualityComparerHelpers.cs
Outdated
Show resolved
Hide resolved
src/coreclr/System.Private.CoreLib/src/System/Collections/Generic/ComparerHelpers.cs
Show resolved
Hide resolved
src/coreclr/System.Private.CoreLib/src/System/Collections/Generic/ComparerHelpers.cs
Outdated
Show resolved
Hide resolved
src/coreclr/System.Private.CoreLib/src/System/Collections/Generic/ComparerHelpers.cs
Show resolved
Hide resolved
@jkotas Roslyn seems to be emitting some invalid IL for the enum tests I've added, going to report it. Should I remove them for now? |
Some examples: Line 118 in e80ef86
This one seems okay? Line 25 in e80ef86
This one doesn't however, since we don't have a type here since the type is shared:
|
I think you want to reorder the checks in
|
Would it make sense to split the JIT change into separate PR? The Mono, VM and BCL changes in this PR are good and they can go in. The JIT change may need some more work, and it may be a good idea to do it separately so that it is easier to look at its impact from jit-diffs. |
TypeHandle resultTh = ((TypeHandle)targetClass->GetCanonicalMethodTable()).Instantiate(inst); | ||
return CORINFO_CLASS_HANDLE(resultTh.GetMethodTable()); | ||
} | ||
if (elemTypeHnd.IsCanonicalSubtype()) |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
The change looks good to me otherwise. Thank you! |
It should be one more case in src/tests/JIT/opt/Devirtualization/Comparer_get_Default.cs like the cases that are there already. The JIT tests are run both with optimizations on and off, so we would find the bug that way. |
I'd prefer that to be in a separate PR after this gets merged to keep diffs separate and avoid any potential issues. |
I am going to leave this for .NET 9. |
I'm not sure why tests didn't fail here, but this appears to have broken some System.Runtime.Serialization.Formatters tests:
|
Presumably runtime/src/libraries/System.Private.CoreLib/src/System/Collections/Generic/EqualityComparer.cs Lines 243 to 250 in 68b410f
for T being byte it should report |
@jkotas do we want that or maybe changing the tests to expect GenericEqualityComparer would be preferred here? |
I'm not particularly concerned about someone serializing the comparer on .NET 8 and when deserialized on .NET Framework having it be a different kind of comparer than it would otherwise have been, especially as BinaryFormatter is on its way out. @GrabYourPitchforks, do we need to fix anything here? |
Binary serialization of compares does not guarantee that you get the fastest possible comparer after deserialization. It only guarantees that you get a functionally equivalent comparer.
It is not the case for |
Did anybody figure this out btw? |
The relevant test is part of the libraries tests, and I believe this PR ended up triggering legs for the libraries tests that ran either on mono or on a checked coreclr, and the particular test suite is disabled for checked coreclr because it's too slow. |
Unifies (Equality)Comparer.Default logic so that it matches between CoreCLR, NativeAOT, Mono in runtime and intrinsic implementations.
Fixes devirt behaviour around Nullable types.
Also enables devirt for non final types in CoreCLR and NativeAOT.
Required for #87579.
Fixes #87391.