From ef257917acdfe727344f68e6ca2a8c17dfeb5c6d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 4 Apr 2026 04:47:28 +0000 Subject: [PATCH 1/7] Initial plan From e35fa6af82598614711c487dc33deb72e6df0b47 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 4 Apr 2026 05:12:36 +0000 Subject: [PATCH 2/7] Cache version-resilient hash code on MethodTableAuxiliaryData to fix superlinear behavior Fixes exponential time complexity in GetVersionResilientTypeHashCode for deeply nested generic type instantiations (e.g., polymorphic recursion). The hash code is now lazily computed and stored in m_cachedVersionResilientHashCode on MethodTableAuxiliaryData. A stored value of 0 indicates the field hasn't been initialized yet. The memory is zero-initialized by the loader heap, so no explicit initialization is required for new instantiations. Before fix: n=31 took ~15s (exponential growth) After fix: n=31 takes ~4ms (constant time) Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/3a3fba93-8075-497c-bb07-a3fe274858a9 Co-authored-by: davidwrighton <10779849+davidwrighton@users.noreply.github.com> --- src/coreclr/vm/methodtable.h | 4 ++++ src/coreclr/vm/versionresilienthashcode.cpp | 15 ++++++++++++--- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/src/coreclr/vm/methodtable.h b/src/coreclr/vm/methodtable.h index 23cc148a09fe57..9e326b1c6a7b2e 100644 --- a/src/coreclr/vm/methodtable.h +++ b/src/coreclr/vm/methodtable.h @@ -358,6 +358,10 @@ struct MethodTableAuxiliaryData // Unloadable context: slot index in LoaderAllocator's pinned table RUNTIMETYPEHANDLE m_hExposedClassObject; + // 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. + int m_cachedVersionResilientHashCode; + #ifdef _DEBUG enum { diff --git a/src/coreclr/vm/versionresilienthashcode.cpp b/src/coreclr/vm/versionresilienthashcode.cpp index ad090be540c7bc..06a93506b91f48 100644 --- a/src/coreclr/vm/versionresilienthashcode.cpp +++ b/src/coreclr/vm/versionresilienthashcode.cpp @@ -122,6 +122,12 @@ int GetVersionResilientTypeHashCode(TypeHandle type) _ASSERTE(!pMT->IsArray()); _ASSERTE(!IsNilToken(pMT->GetCl())); + int cachedHashCode = VolatileLoadWithoutBarrier(&pMT->GetAuxiliaryData()->m_cachedVersionResilientHashCode); + if (cachedHashCode != 0) + { + return cachedHashCode; + } + LPCUTF8 szNamespace; LPCUTF8 szName; IfFailThrow(pMT->GetMDImport()->GetNameOfTypeDef(pMT->GetCl(), &szName, &szNamespace)); @@ -135,13 +141,16 @@ int GetVersionResilientTypeHashCode(TypeHandle type) if (!pMT->IsGenericTypeDefinition() && pMT->HasInstantiation()) { - return ComputeGenericInstanceHashCode(hashcode, + hashcode = ComputeGenericInstanceHashCode(hashcode, pMT->GetInstantiation().GetNumArgs(), pMT->GetInstantiation(), GetVersionResilientTypeHashCode); } - else + + if (hashcode != 0) { - return hashcode; + VolatileStore(&pMT->GetAuxiliaryDataForWrite()->m_cachedVersionResilientHashCode, hashcode); } + + return hashcode; } else if (type.IsPointer()) From 7596df2c9cb7cf51f55fc6622ba2a1905d6662b2 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 4 Apr 2026 05:14:39 +0000 Subject: [PATCH 3/7] Add comment explaining why zero hash codes are not cached Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/3a3fba93-8075-497c-bb07-a3fe274858a9 Co-authored-by: davidwrighton <10779849+davidwrighton@users.noreply.github.com> --- src/coreclr/vm/versionresilienthashcode.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/coreclr/vm/versionresilienthashcode.cpp b/src/coreclr/vm/versionresilienthashcode.cpp index 06a93506b91f48..1567118c770596 100644 --- a/src/coreclr/vm/versionresilienthashcode.cpp +++ b/src/coreclr/vm/versionresilienthashcode.cpp @@ -145,6 +145,9 @@ int GetVersionResilientTypeHashCode(TypeHandle type) 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); From d9e391149f9e3e84546f970554f8082ab65a5bd3 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 4 Apr 2026 14:20:06 +0000 Subject: [PATCH 4/7] Move m_cachedVersionResilientHashCode to fill existing 4-byte padding slot Place the int field immediately after the union to fill the 4-byte implicit alignment padding that already exists between the union (4 bytes) and m_pLoaderModule (8-byte aligned pointer) on 64-bit platforms. sizeof(MethodTableAuxiliaryData) remains 24 bytes before and after this change on 64-bit release builds, so the memory overhead is 0 bytes / 0%. Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/c5ceb726-3055-4d02-a92f-e0d52438fab9 Co-authored-by: davidwrighton <10779849+davidwrighton@users.noreply.github.com> --- src/coreclr/vm/methodtable.h | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/coreclr/vm/methodtable.h b/src/coreclr/vm/methodtable.h index 9e326b1c6a7b2e..982b747866c166 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; @@ -358,10 +363,6 @@ struct MethodTableAuxiliaryData // Unloadable context: slot index in LoaderAllocator's pinned table RUNTIMETYPEHANDLE m_hExposedClassObject; - // 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. - int m_cachedVersionResilientHashCode; - #ifdef _DEBUG enum { From f8a7695e1f4b5ae0ccee44e7cff58be5cc9329d0 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 4 Apr 2026 14:44:05 +0000 Subject: [PATCH 5/7] Extend version-resilient hash cache to array MethodTables Arrays are also backed by MethodTable (TypeHandle::IsArray() returns !IsTypeDesc() && AsMethodTable()->IsArray()), so they have the same MethodTableAuxiliaryData and can benefit from the same caching mechanism. Restructure GetVersionResilientTypeHashCode to handle both arrays and non-array types in the single !type.IsTypeDesc() branch, checking/storing the cache before and after the type-specific hash computation. Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/1e4f951d-0276-47ac-8fc3-7d5609071844 Co-authored-by: davidwrighton <10779849+davidwrighton@users.noreply.github.com> --- src/coreclr/vm/versionresilienthashcode.cpp | 40 +++++++++++---------- 1 file changed, 21 insertions(+), 19 deletions(-) diff --git a/src/coreclr/vm/versionresilienthashcode.cpp b/src/coreclr/vm/versionresilienthashcode.cpp index 1567118c770596..adec6fb8031fa1 100644 --- a/src/coreclr/vm/versionresilienthashcode.cpp +++ b/src/coreclr/vm/versionresilienthashcode.cpp @@ -110,17 +110,11 @@ 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())); + _ASSERTE(pMT->IsArray() || !IsNilToken(pMT->GetCl())); int cachedHashCode = VolatileLoadWithoutBarrier(&pMT->GetAuxiliaryData()->m_cachedVersionResilientHashCode); if (cachedHashCode != 0) @@ -128,21 +122,29 @@ int GetVersionResilientTypeHashCode(TypeHandle type) return cachedHashCode; } - LPCUTF8 szNamespace; - LPCUTF8 szName; - IfFailThrow(pMT->GetMDImport()->GetNameOfTypeDef(pMT->GetCl(), &szName, &szNamespace)); - int hashcode = ComputeNameHashCode(szNamespace, szName); - - MethodTable *pMTEnclosing = pMT->LoadEnclosingMethodTable(CLASS_LOAD_APPROXPARENTS); - if (pMTEnclosing != NULL) + int hashcode; + if (pMT->IsArray()) { - hashcode = ComputeNestedTypeHashCode(GetVersionResilientTypeHashCode(TypeHandle(pMTEnclosing)), hashcode); + hashcode = ComputeArrayTypeHashCode(GetVersionResilientTypeHashCode(type.GetArrayElementTypeHandle()), type.GetRank()); } - - if (!pMT->IsGenericTypeDefinition() && pMT->HasInstantiation()) + else { - hashcode = ComputeGenericInstanceHashCode(hashcode, - pMT->GetInstantiation().GetNumArgs(), pMT->GetInstantiation(), GetVersionResilientTypeHashCode); + 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. From ee4e1b844785b7a68fb27d5c1676cf2848f95ba7 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 9 Apr 2026 05:31:15 +0000 Subject: [PATCH 6/7] Fix 32-bit layout mismatch: add missing field to managed MethodTableAuxiliaryData The managed mirror of MethodTableAuxiliaryData in RuntimeHelpers.CoreCLR.cs was missing the new m_cachedVersionResilientHashCode field. On 32-bit platforms (x86, ARM32, WASM), pointer fields are 4 bytes and there is no alignment padding between the flags union and the pointer fields. The extra 4-byte field in the C++ struct shifted LoaderModule and ExposedClassObjectRaw by 4 bytes relative to what the managed struct expected, corrupting all access to these fields on 32-bit platforms. Fix by adding the corresponding private field to the managed struct so its layout exactly matches the C++ struct on all platforms: - 64-bit: same as before (fills existing alignment padding, size unchanged) - 32-bit: struct grows from 12 to 16 bytes on both sides, offsets now agree Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/71f12275-cf7d-46ac-a8cd-53247cc1990d Co-authored-by: jkotas <6668460+jkotas@users.noreply.github.com> --- .../System/Runtime/CompilerServices/RuntimeHelpers.CoreCLR.cs | 1 + 1 file changed, 1 insertion(+) 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..cb86ec7d47bdb9 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; From 85aa88e2b80bc0dd3ddea69ded21f51af065c77d Mon Sep 17 00:00:00 2001 From: Jan Kotas Date: Wed, 8 Apr 2026 22:34:02 -0700 Subject: [PATCH 7/7] Apply suggestion from @jkotas --- .../System/Runtime/CompilerServices/RuntimeHelpers.CoreCLR.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 cb86ec7d47bdb9..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,7 +1125,7 @@ internal ref struct ThreadStaticsInfo internal unsafe struct MethodTableAuxiliaryData { private uint Flags; - private int _cachedVersionResilientHashCode; + private int CachedVersionResilientHashCode; private void* LoaderModule; private nint ExposedClassObjectRaw;