Skip to content
This repository was archived by the owner on Jan 23, 2023. It is now read-only.

Conversation

@MichalStrehovsky
Copy link
Member

A real world app prompted me to look into this. I know this is not a huge deal because EqualityComparer<T>.Default has special casing for enums, but it's actually pretty easy to hit this anyway (even System.Private.CoreLib has a couple hits) and people on the internet are always puzzled about this one.

enum Mine { One, Two }

class Program
{
    static void Main()
    {
        return Mine.Two.GetHashCode();
    }
}

The code generated for the above has an unexpected boxing operation in it. I say unexpected, because e.g. 1.GetHashCode() wouldn't have boxing.

The reason why we end up with boxing is because in the constrained call to System.Object::GetHashCode, the constraint resolution ends up resolving the method to System.Enum::GetHashCode() and System.Enum is a reference type. Bodies for instance methods on reference types expect a boxed this, while bodies on value types expect a byref.

I'm patching it up to pretend the constrained call was to the underlying type's GetHashCode method. I don't pretend it's a great solution and unfortunately it won't cover Equals. I'll understand if there's pushback against merging this.

Before the change

G_M15495_IG01:
       4883EC28             sub      rsp, 40
       90                   nop

G_M15495_IG02:
       488D0D00000000       lea      rcx, [(reloc)]
       E800000000           call     CORINFO_HELP_NEWSFAST
       C7400801000000       mov      dword ptr [rax+8], 1
       488BC8               mov      rcx, rax
       488B00               mov      rax, qword ptr [rax]
       488B4048             mov      rax, qword ptr [rax+72]
       FF5010               call     qword ptr [rax+16]System.Object:GetHashCode():int:this
       90                   nop

G_M15495_IG03:
       4883C428             add      rsp, 40
       C3                   ret

After the change

G_M15496_IG01:
       0F1F440000           nop

G_M15496_IG02:
       B801000000           mov      eax, 1

G_M15496_IG03:
       C3                   ret

// We can't do this for any other method since ToString and Equals have different semantics for enums
// and their underlying type.
LPCUTF8 pMethodName = pMD->GetName();
if (strcmp(pMethodName, "GetHashCode") == 0)
Copy link
Member

Choose a reason for hiding this comment

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

pMD->GetSlot() == MscorlibBinder::GetMethod(METHOD__OBJECT__GET_HASH_CODE)->GetSlot() would be a bit more efficient.

Copy link
Member

@jkotas jkotas left a comment

Choose a reason for hiding this comment

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

Nice 👍

@jkotas jkotas merged commit f4ba6ee into dotnet:master Oct 31, 2016
@MichalStrehovsky MichalStrehovsky deleted the doNotBoxEnumGetHashCode branch October 31, 2016 15:35
MichalStrehovsky added a commit to MichalStrehovsky/corert that referenced this pull request Nov 1, 2016
Generate synthetic instance methods into which constrained calls
can resolve. This prevents boxing 'this' that would be required
before a call to the System.Enum's implementation.

This is specifically not a synthetic virtual override. The thunk
only has very little perf advantage over System.Enum's implementation
if 'this' is already boxed and is not worth the size overhead.

This is an extended port of dotnet/coreclr#7895
MichalStrehovsky added a commit to MichalStrehovsky/corert that referenced this pull request Mar 17, 2017
Generate synthetic instance methods into which constrained calls
can resolve. This prevents boxing 'this' that would be required
before a call to the System.Enum's implementation.

This is specifically not a synthetic virtual override. The thunk
only has very little perf advantage over System.Enum's implementation
if 'this' is already boxed and is not worth the size overhead.

This is an extended port of dotnet/coreclr#7895

Fixes dotnet#2111.
@karelz karelz modified the milestone: 2.0.0 Aug 28, 2017
Forgind pushed a commit to dotnet/msbuild that referenced this pull request Aug 14, 2020
Calling GetHashCode() on an enum results in boxing (i.e. a GC allocation) when running on .NET Framework. This has been addressed in .NET Core (dotnet/coreclr#7895) but we want to fix it on our side still because it's showing in Visual Studio profiles:
https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1166715

This PR is also removing the calls to base.GetHashCode() which are pointless in structures that calculate the hashcode out of all their fields.
sharwell added a commit to sharwell/roslyn that referenced this pull request Mar 29, 2021
Boxing was eliminated for calls to GetHashCode() on enum types in
dotnet/coreclr#7895. This commit updates SegmentedDictionary<TKey, TValue>
to avoid assuming this path is optimized for netstandard2.0 builds,
since they might run on .NET Framework.
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants