diff --git a/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.CoreCLR.cs index b8b799bf6c45e2..7d30dc1b187a82 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.CoreCLR.cs @@ -1125,6 +1125,7 @@ internal ref struct ThreadStaticsInfo internal unsafe struct MethodTableAuxiliaryData { private uint Flags; + private int CachedVersionResilientHashCode; private void* LoaderModule; private nint ExposedClassObjectRaw; diff --git a/src/coreclr/vm/methodtable.h b/src/coreclr/vm/methodtable.h index b4bd831b87f604..8001f1a4597592 100644 --- a/src/coreclr/vm/methodtable.h +++ b/src/coreclr/vm/methodtable.h @@ -351,6 +351,11 @@ struct MethodTableAuxiliaryData }; }; + // Lazily initialized cache for the version-resilient hash code of this MethodTable. + // A stored value of 0 indicates the field hasn't been set yet. + // Placed here to fill the 4-byte alignment padding between m_dwFlags and m_pLoaderModule, + // so this field adds no extra size to the struct on 64-bit platforms. + int m_cachedVersionResilientHashCode; PTR_Module m_pLoaderModule; diff --git a/src/coreclr/vm/versionresilienthashcode.cpp b/src/coreclr/vm/versionresilienthashcode.cpp index ad090be540c7bc..adec6fb8031fa1 100644 --- a/src/coreclr/vm/versionresilienthashcode.cpp +++ b/src/coreclr/vm/versionresilienthashcode.cpp @@ -110,38 +110,52 @@ int GetVersionResilientTypeHashCode(TypeHandle type) { STANDARD_VM_CONTRACT; - if (type.IsArray()) - { - return ComputeArrayTypeHashCode(GetVersionResilientTypeHashCode(type.GetArrayElementTypeHandle()), type.GetRank()); - } - else if (!type.IsTypeDesc()) { MethodTable *pMT = type.AsMethodTable(); - _ASSERTE(!pMT->IsArray()); - _ASSERTE(!IsNilToken(pMT->GetCl())); - - LPCUTF8 szNamespace; - LPCUTF8 szName; - IfFailThrow(pMT->GetMDImport()->GetNameOfTypeDef(pMT->GetCl(), &szName, &szNamespace)); - int hashcode = ComputeNameHashCode(szNamespace, szName); + _ASSERTE(pMT->IsArray() || !IsNilToken(pMT->GetCl())); - MethodTable *pMTEnclosing = pMT->LoadEnclosingMethodTable(CLASS_LOAD_APPROXPARENTS); - if (pMTEnclosing != NULL) + int cachedHashCode = VolatileLoadWithoutBarrier(&pMT->GetAuxiliaryData()->m_cachedVersionResilientHashCode); + if (cachedHashCode != 0) { - hashcode = ComputeNestedTypeHashCode(GetVersionResilientTypeHashCode(TypeHandle(pMTEnclosing)), hashcode); + return cachedHashCode; } - if (!pMT->IsGenericTypeDefinition() && pMT->HasInstantiation()) + int hashcode; + if (pMT->IsArray()) { - return ComputeGenericInstanceHashCode(hashcode, - pMT->GetInstantiation().GetNumArgs(), pMT->GetInstantiation(), GetVersionResilientTypeHashCode); + hashcode = ComputeArrayTypeHashCode(GetVersionResilientTypeHashCode(type.GetArrayElementTypeHandle()), type.GetRank()); } else { - return hashcode; + LPCUTF8 szNamespace; + LPCUTF8 szName; + IfFailThrow(pMT->GetMDImport()->GetNameOfTypeDef(pMT->GetCl(), &szName, &szNamespace)); + hashcode = ComputeNameHashCode(szNamespace, szName); + + MethodTable *pMTEnclosing = pMT->LoadEnclosingMethodTable(CLASS_LOAD_APPROXPARENTS); + if (pMTEnclosing != NULL) + { + hashcode = ComputeNestedTypeHashCode(GetVersionResilientTypeHashCode(TypeHandle(pMTEnclosing)), hashcode); + } + + if (!pMT->IsGenericTypeDefinition() && pMT->HasInstantiation()) + { + hashcode = ComputeGenericInstanceHashCode(hashcode, + pMT->GetInstantiation().GetNumArgs(), pMT->GetInstantiation(), GetVersionResilientTypeHashCode); + } + } + + // 0 is used as the sentinel "not yet cached" value, so only cache if non-zero. + // Types with a hash code of exactly 0 are extremely rare in practice; they will simply + // have their hash code recomputed on every call, which is still correct behavior. + if (hashcode != 0) + { + VolatileStore(&pMT->GetAuxiliaryDataForWrite()->m_cachedVersionResilientHashCode, hashcode); } + + return hashcode; } else if (type.IsPointer())