From 0001271afbd2483a14f8f6b90662b658957509b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Strehovsk=C3=BD?= Date: Tue, 23 Jun 2026 16:02:03 +0900 Subject: [PATCH 1/2] Fix building unboxing and real generic method targets at the same time When a single type load needs both an unboxing and non-unboxing entrypoint at the same time, we'd build the same generic dictionary twice and fail during registration. The surgical fix is to allow type loader to do the work twice, but stop at the point where we would try to allocate it. Runtime type loader having unboxing MethodDescs was a mistake. This is not a problem if we don't need both at the same time since the all the lookups for existing dictionary already ignore the unboxing bit. (Which is why we fail during registration in the bug - unboxing bit is not part of the key.) No regression test because the test is in #128702. --- .../Runtime/TypeLoader/TypeBuilder.cs | 69 ++++++++++++++++++- 1 file changed, 66 insertions(+), 3 deletions(-) diff --git a/src/coreclr/nativeaot/System.Private.TypeLoader/src/Internal/Runtime/TypeLoader/TypeBuilder.cs b/src/coreclr/nativeaot/System.Private.TypeLoader/src/Internal/Runtime/TypeLoader/TypeBuilder.cs index 8c57c42f65bfbb..23ed319b05c7e2 100644 --- a/src/coreclr/nativeaot/System.Private.TypeLoader/src/Internal/Runtime/TypeLoader/TypeBuilder.cs +++ b/src/coreclr/nativeaot/System.Private.TypeLoader/src/Internal/Runtime/TypeLoader/TypeBuilder.cs @@ -797,6 +797,14 @@ private void FinishRuntimeType(TypeDesc type) for (int i = 0; i < _methodsThatNeedDictionaries.Count; i++) { InstantiatedMethod method = _methodsThatNeedDictionaries[i]; + + // If this type load is building both unboxing and non-unboxing entrypoint, we only need + // to register one of them because the generic dictionary is the same (and registration discards the unbox distinction). + if (method.UnboxingStub && IsNonUnboxingDictionaryBeingBuilt(method, out _)) + { + continue; + } + yield return new TypeLoaderEnvironment.GenericMethodEntry { _declaringTypeHandle = GetRuntimeTypeHandle(method.OwningType), @@ -818,16 +826,34 @@ private void RegisterGenericTypesAndMethods() typesToRegisterCount++; } + int methodsToRegisterCount = 0; + for (int i = 0; i < _methodsThatNeedDictionaries.Count; i++) + { + if (!_methodsThatNeedDictionaries[i].UnboxingStub + || !IsNonUnboxingDictionaryBeingBuilt(_methodsThatNeedDictionaries[i], out _)) + { + methodsToRegisterCount++; + } + } + var registrationData = new TypeLoaderEnvironment.DynamicGenericsRegistrationData { TypesToRegisterCount = typesToRegisterCount, TypesToRegister = (typesToRegisterCount != 0) ? TypesToRegister() : null, - MethodsToRegisterCount = _methodsThatNeedDictionaries.Count, + MethodsToRegisterCount = methodsToRegisterCount, MethodsToRegister = (_methodsThatNeedDictionaries.Count != 0) ? MethodsToRegister() : null, }; TypeLoaderEnvironment.Instance.RegisterDynamicGenericTypesAndMethods(registrationData); } + private static bool IsNonUnboxingDictionaryBeingBuilt(InstantiatedMethod method, out nint dictionary) + { + Debug.Assert(method.UnboxingStub); + InstantiatedMethod unboxTargetMethod = (InstantiatedMethod)method.Context.ResolveGenericMethodInstantiation(false, method.AsyncVariant, method.ReturnDroppingAsyncThunk, (DefType)method.OwningType, method.NameAndSignature, method.Instantiation); + dictionary = unboxTargetMethod.RuntimeMethodDictionary; + return dictionary != 0; + } + private void FinishTypeAndMethodBuilding() { // Once we start allocating EETypes and dictionaries, the only accepted failure is OOM. @@ -844,9 +870,36 @@ private void FinishTypeAndMethodBuilding() AllocateRuntimeType(_typesThatNeedTypeHandles[i]); } + // Allocate dictionaries for non-unboxing methods first. for (int i = 0; i < _methodsThatNeedDictionaries.Count; i++) { - AllocateRuntimeMethodDictionary(_methodsThatNeedDictionaries[i]); + InstantiatedMethod method = _methodsThatNeedDictionaries[i]; + if (method.UnboxingStub) + continue; + + AllocateRuntimeMethodDictionary(method); + } + + // Now look at the unboxing ones. + for (int i = 0; i < _methodsThatNeedDictionaries.Count; i++) + { + InstantiatedMethod method = _methodsThatNeedDictionaries[i]; + if (!method.UnboxingStub) + continue; + + if (IsNonUnboxingDictionaryBeingBuilt(method, out nint dictionary)) + { + // The dictionary between the unboxing and non-unboxing variant is the same, we must not + // build a new one. This situation can happen if we need both the unboxing and non-unboxing variants + // as part of the same type build. + // The lookups for existing dictionaries ignore the UnboxingStub bit, so if one flavor was built statically + // or dynamically before this type load, we wouldn't reach here. + method.AssociateWithRuntimeMethodDictionary(dictionary); + } + else + { + AllocateRuntimeMethodDictionary(method); + } } // Do not add more type phases here. Instead, read the required information from the TypeDesc or TypeBuilderState. @@ -864,7 +917,17 @@ private void FinishTypeAndMethodBuilding() for (int i = 0; i < _methodsThatNeedDictionaries.Count; i++) { - FinishMethodDictionary(_methodsThatNeedDictionaries[i]); + InstantiatedMethod method = _methodsThatNeedDictionaries[i]; + + // If this type load is building both unboxing and non-unboxing entrypoint, the unboxing flavor + // is using the generic dictionary of the non-unboxing entrypoint (they are the same thing). + // The dictionary will be finished by the non-unboxing entrypoint. + if (method.UnboxingStub && IsNonUnboxingDictionaryBeingBuilt(method, out _)) + { + continue; + } + + FinishMethodDictionary(method); } int newArrayTypesCount = 0; From f2abb1321c4d93b3dd3d8ca3e618fd83e7f3abb2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Strehovsk=C3=BD?= Date: Tue, 23 Jun 2026 20:47:52 +0900 Subject: [PATCH 2/2] Fix condition for MethodsToRegister assignment --- .../src/Internal/Runtime/TypeLoader/TypeBuilder.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/coreclr/nativeaot/System.Private.TypeLoader/src/Internal/Runtime/TypeLoader/TypeBuilder.cs b/src/coreclr/nativeaot/System.Private.TypeLoader/src/Internal/Runtime/TypeLoader/TypeBuilder.cs index 23ed319b05c7e2..fbfd71f31e7b84 100644 --- a/src/coreclr/nativeaot/System.Private.TypeLoader/src/Internal/Runtime/TypeLoader/TypeBuilder.cs +++ b/src/coreclr/nativeaot/System.Private.TypeLoader/src/Internal/Runtime/TypeLoader/TypeBuilder.cs @@ -841,7 +841,7 @@ private void RegisterGenericTypesAndMethods() TypesToRegisterCount = typesToRegisterCount, TypesToRegister = (typesToRegisterCount != 0) ? TypesToRegister() : null, MethodsToRegisterCount = methodsToRegisterCount, - MethodsToRegister = (_methodsThatNeedDictionaries.Count != 0) ? MethodsToRegister() : null, + MethodsToRegister = (methodsToRegisterCount != 0) ? MethodsToRegister() : null, }; TypeLoaderEnvironment.Instance.RegisterDynamicGenericTypesAndMethods(registrationData); }