From 63caaad99e91b55fe1fd0702b11a168a5ec36361 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 20 Apr 2026 05:51:12 +0000 Subject: [PATCH 1/9] Initial plan From fe6adada36008dc05c7790a0d6367a899704c0fb Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 20 Apr 2026 06:31:21 +0000 Subject: [PATCH 2/9] Preserve array trim targets in ExternalTypeMap and add H regression test Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/b403556c-8176-43d3-a272-e0f2dd338c46 Co-authored-by: MichalStrehovsky <13110571+MichalStrehovsky@users.noreply.github.com> --- .../DependencyAnalysis/ExternalTypeMapNode.cs | 22 +++++++++++++++---- .../TrimmingBehaviors/DeadCodeElimination.cs | 11 +++++++++- 2 files changed, 28 insertions(+), 5 deletions(-) diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ExternalTypeMapNode.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ExternalTypeMapNode.cs index 9fa357c3329b8c..26b459913c644d 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ExternalTypeMapNode.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ExternalTypeMapNode.cs @@ -55,6 +55,20 @@ public override IEnumerable GetConditionalStaticDep context.NativeLayout.TemplateTypeLayout(canonTrimmingType), "External type map trim target that could be loaded at runtime"); } + else if (effectiveTrimTargetType is ArrayType arrayType) + { + // Some arrays don't have array templates (e.g. MD arrays, arrays of pointers). + // If the element type is template-loadable, the runtime can still construct the array type. + TypeDesc effectiveElementType = GetEffectiveTrimTargetType(arrayType.ElementType); + TypeDesc canonElementType = effectiveElementType.ConvertToCanonForm(CanonicalFormKind.Specific); + if (canonElementType != effectiveElementType && GenericTypesTemplateMap.IsEligibleToHaveATemplate(canonElementType)) + { + yield return new CombinedDependencyListEntry( + context.NecessaryTypeSymbol(effectiveTrimTargetType), + context.NativeLayout.TemplateTypeLayout(canonElementType), + "External type map array trim target with template-loadable element type"); + } + } } } } @@ -100,12 +114,12 @@ public override int CompareToImpl(ISortableNode other, CompilerComparer comparer } } - // Strip parameterized type wrappers (arrays, pointers, byrefs) to get the effective - // type for trimming purposes. If the trim target is Foo[], the TypeMap entry should be - // included when Foo is reachable, matching ILLink's TypeMapHandler stripping behavior. + // Strip non-array parameterized wrappers (pointers, byrefs) to get the effective + // trimming target type. Arrays are preserved so trim dependencies can be conditioned + // on array existence rather than just element type reachability. private static TypeDesc GetEffectiveTrimTargetType(TypeDesc trimmingTargetType) { - while (trimmingTargetType is ParameterizedType parameterized) + while (trimmingTargetType is ParameterizedType parameterized && !trimmingTargetType.IsArray) trimmingTargetType = parameterized.ParameterType; return trimmingTargetType; } diff --git a/src/tests/nativeaot/SmokeTests/TrimmingBehaviors/DeadCodeElimination.cs b/src/tests/nativeaot/SmokeTests/TrimmingBehaviors/DeadCodeElimination.cs index b8cfacbf4f4bfe..11b5fb8782ae76 100644 --- a/src/tests/nativeaot/SmokeTests/TrimmingBehaviors/DeadCodeElimination.cs +++ b/src/tests/nativeaot/SmokeTests/TrimmingBehaviors/DeadCodeElimination.cs @@ -23,6 +23,7 @@ [assembly: TypeMap("E", typeof(DeadCodeElimination.TestInteropMapArrayTrimming.Target5), typeof(DeadCodeElimination.TestInteropMapArrayTrimming.TrimTarget5[]))] [assembly: TypeMap("F", typeof(DeadCodeElimination.TestInteropMapArrayTrimming.Target6), typeof(DeadCodeElimination.TestInteropMapArrayTrimming.TrimTarget6[]))] [assembly: TypeMap("G", typeof(DeadCodeElimination.TestInteropMapArrayTrimming.Target7), typeof(DeadCodeElimination.TestInteropMapArrayTrimming.TrimTarget7[]))] +[assembly: TypeMap("H", typeof(DeadCodeElimination.TestInteropMapArrayTrimming.Target8), typeof(DeadCodeElimination.TestInteropMapArrayTrimming.TrimTarget8[]))] class DeadCodeElimination { @@ -1387,6 +1388,7 @@ public struct TrimTarget4; public struct TrimTarget5; public struct TrimTarget6; public struct TrimTarget7; + public struct TrimTarget8; public class Target1; public class Target2; public class Target3; @@ -1394,6 +1396,7 @@ public class Target4; public class Target5; public class Target6; public class Target7; + public class Target8; public class Atom; public static unsafe object[] MakeGenerics() @@ -1420,6 +1423,10 @@ public static void Run() { Console.WriteLine("Unexpected"); } + if (GetUnknown() is TrimTarget8) + { + Console.WriteLine("Unexpected"); + } typeof(TestInteropMapArrayTrimming).GetMethod(nameof(MakeGenerics)).MakeGenericMethod([GetAtom()]).Invoke(null, []); @@ -1446,11 +1453,13 @@ public static void Run() throw new Exception("Expected entry G"); ThrowIfUsableMethodTable(typeG); - // D, E: element type is unreachable — entries must be absent + // D, E, H: array trim targets are unreachable — entries must be absent if (map.TryGetValue("D", out _)) throw new Exception("Unexpected entry D"); if (map.TryGetValue("E", out _)) throw new Exception("Unexpected entry E"); + if (map.TryGetValue("H", out _)) + throw new Exception("Unexpected entry H"); } } From 41ffdf3b3c9204a42c441e701afe547b4db0c217 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 20 Apr 2026 06:32:23 +0000 Subject: [PATCH 3/9] Clarify array-template and H-case comments Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/b403556c-8176-43d3-a272-e0f2dd338c46 Co-authored-by: MichalStrehovsky <13110571+MichalStrehovsky@users.noreply.github.com> --- .../Compiler/DependencyAnalysis/ExternalTypeMapNode.cs | 2 +- .../SmokeTests/TrimmingBehaviors/DeadCodeElimination.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ExternalTypeMapNode.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ExternalTypeMapNode.cs index 26b459913c644d..c8899233235494 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ExternalTypeMapNode.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ExternalTypeMapNode.cs @@ -57,7 +57,7 @@ public override IEnumerable GetConditionalStaticDep } else if (effectiveTrimTargetType is ArrayType arrayType) { - // Some arrays don't have array templates (e.g. MD arrays, arrays of pointers). + // Some arrays don't have array templates (e.g. multidimensional arrays, arrays of pointers). // If the element type is template-loadable, the runtime can still construct the array type. TypeDesc effectiveElementType = GetEffectiveTrimTargetType(arrayType.ElementType); TypeDesc canonElementType = effectiveElementType.ConvertToCanonForm(CanonicalFormKind.Specific); diff --git a/src/tests/nativeaot/SmokeTests/TrimmingBehaviors/DeadCodeElimination.cs b/src/tests/nativeaot/SmokeTests/TrimmingBehaviors/DeadCodeElimination.cs index 11b5fb8782ae76..965a4772565258 100644 --- a/src/tests/nativeaot/SmokeTests/TrimmingBehaviors/DeadCodeElimination.cs +++ b/src/tests/nativeaot/SmokeTests/TrimmingBehaviors/DeadCodeElimination.cs @@ -1453,7 +1453,7 @@ public static void Run() throw new Exception("Expected entry G"); ThrowIfUsableMethodTable(typeG); - // D, E, H: array trim targets are unreachable — entries must be absent + // D, E: trim target element type is unreachable. H: element is reachable, but TrimTarget8[] is unreachable. if (map.TryGetValue("D", out _)) throw new Exception("Unexpected entry D"); if (map.TryGetValue("E", out _)) From 4c2a5daa576bf3e1a08d1105f94f49ce6c0ba63c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Strehovsk=C3=BD?= Date: Tue, 21 Apr 2026 11:20:30 +0900 Subject: [PATCH 4/9] Add test --- .../TrimmingBehaviors/DeadCodeElimination.cs | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/tests/nativeaot/SmokeTests/TrimmingBehaviors/DeadCodeElimination.cs b/src/tests/nativeaot/SmokeTests/TrimmingBehaviors/DeadCodeElimination.cs index 965a4772565258..808236789e05d3 100644 --- a/src/tests/nativeaot/SmokeTests/TrimmingBehaviors/DeadCodeElimination.cs +++ b/src/tests/nativeaot/SmokeTests/TrimmingBehaviors/DeadCodeElimination.cs @@ -24,6 +24,7 @@ [assembly: TypeMap("F", typeof(DeadCodeElimination.TestInteropMapArrayTrimming.Target6), typeof(DeadCodeElimination.TestInteropMapArrayTrimming.TrimTarget6[]))] [assembly: TypeMap("G", typeof(DeadCodeElimination.TestInteropMapArrayTrimming.Target7), typeof(DeadCodeElimination.TestInteropMapArrayTrimming.TrimTarget7[]))] [assembly: TypeMap("H", typeof(DeadCodeElimination.TestInteropMapArrayTrimming.Target8), typeof(DeadCodeElimination.TestInteropMapArrayTrimming.TrimTarget8[]))] +[assembly: TypeMap("I", typeof(DeadCodeElimination.TestInteropMapArrayTrimming.Target9), typeof(DeadCodeElimination.TestInteropMapArrayTrimming.TrimTarget9[,]))] class DeadCodeElimination { @@ -1389,6 +1390,7 @@ public struct TrimTarget5; public struct TrimTarget6; public struct TrimTarget7; public struct TrimTarget8; + public class TrimTarget9; public class Target1; public class Target2; public class Target3; @@ -1397,6 +1399,7 @@ public class Target5; public class Target6; public class Target7; public class Target8; + public class Target9; public class Atom; public static unsafe object[] MakeGenerics() @@ -1417,6 +1420,17 @@ public static unsafe object[] MakeGenerics() [MethodImpl(MethodImplOptions.NoInlining)] static object GetUnknown() => null; + [MethodImpl(MethodImplOptions.NoInlining)] + public static object MakeSharedGeneric() + => new T[1,1]; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool TestSharedGeneric() + => MakeSharedGeneric() is T[,]; + + [MethodImpl(MethodImplOptions.NoInlining)] + static Type GetTrimTarget9() => typeof(TrimTarget9); + public static void Run() { if (GetUnknown() is TrimTarget7[]) @@ -1430,9 +1444,12 @@ public static void Run() typeof(TestInteropMapArrayTrimming).GetMethod(nameof(MakeGenerics)).MakeGenericMethod([GetAtom()]).Invoke(null, []); + typeof(TestInteropMapArrayTrimming).GetMethod(nameof(TestSharedGeneric)).MakeGenericMethod([GetTrimTarget9()]).Invoke(null, []); + var map = TypeMapping.GetOrCreateExternalTypeMapping(); // A, B, C, F, G: trim target element type is reachable — entries must be present + // I: trim target can be constructed at runtime using type loader if (!map.TryGetValue("A", out Type typeA) || typeA.Name != nameof(Target1)) throw new Exception("Expected entry A"); ThrowIfUsableMethodTable(typeA); @@ -1453,6 +1470,10 @@ public static void Run() throw new Exception("Expected entry G"); ThrowIfUsableMethodTable(typeG); + if (!map.TryGetValue("I", out Type typeI) || typeI.Name != nameof(Target9)) + throw new Exception("Expected entry I"); + ThrowIfUsableMethodTable(typeI); + // D, E: trim target element type is unreachable. H: element is reachable, but TrimTarget8[] is unreachable. if (map.TryGetValue("D", out _)) throw new Exception("Unexpected entry D"); From 2c07962ff662fd4e1c060e7ad5c643af59aba6cb Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 21 Apr 2026 02:49:58 +0000 Subject: [PATCH 5/9] Handle MdArray trim targets by conditioning on element type reachability Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/67701700-796d-45b7-9aa8-f035408d84d4 Co-authored-by: MichalStrehovsky <13110571+MichalStrehovsky@users.noreply.github.com> --- .../DependencyAnalysis/ExternalTypeMapNode.cs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ExternalTypeMapNode.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ExternalTypeMapNode.cs index c8899233235494..28dd540f9ff965 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ExternalTypeMapNode.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ExternalTypeMapNode.cs @@ -68,6 +68,17 @@ public override IEnumerable GetConditionalStaticDep context.NativeLayout.TemplateTypeLayout(canonElementType), "External type map array trim target with template-loadable element type"); } + + // Multidimensional arrays can be constructed at runtime from just the element type's + // MethodTable (e.g. via shared generic code that creates T[,]). If the element type + // is reachable, consider the MdArray type reachable as well. + if (!arrayType.IsSzArray) + { + yield return new CombinedDependencyListEntry( + context.NecessaryTypeSymbol(effectiveTrimTargetType), + context.NecessaryTypeSymbol(effectiveElementType), + "MdArray can be constructed at runtime from element type"); + } } } } From 26f09a9e646994072fad7c32d2d0dbd149aa721c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 22 Apr 2026 01:43:04 +0000 Subject: [PATCH 6/9] Fix ILCompiler trimming test: create array type so SzArray trim target is reachable Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/35fcb756-734f-40dc-86f9-4c1bab734508 Co-authored-by: MichalStrehovsky <13110571+MichalStrehovsky@users.noreply.github.com> --- .../illink/test/Mono.Linker.Tests.Cases/Reflection/TypeMap.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tools/illink/test/Mono.Linker.Tests.Cases/Reflection/TypeMap.cs b/src/tools/illink/test/Mono.Linker.Tests.Cases/Reflection/TypeMap.cs index 6027b7c1274e9f..ce887a9d5e8458 100644 --- a/src/tools/illink/test/Mono.Linker.Tests.Cases/Reflection/TypeMap.cs +++ b/src/tools/illink/test/Mono.Linker.Tests.Cases/Reflection/TypeMap.cs @@ -211,7 +211,7 @@ static void ConstrainedStaticCall(T t) where T : IStaticInterface Console.WriteLine(new ArrayElement[1]); - Console.WriteLine(new ArrayTypeTrimTargetClass()); + Console.WriteLine(new ArrayTypeTrimTargetClass[1] { new ArrayTypeTrimTargetClass() }); Console.WriteLine(new ConstructedNoTypeCheckNoBoxStruct(42).Value); From 754bd95ed666d0d027492db53acae65b35b9cf09 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 22 Apr 2026 03:40:21 +0000 Subject: [PATCH 7/9] Update typemap.md: add parameterized type rules for pointer/byref/array trim targets Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/77e6485a-85b9-4747-bb5c-812f79579bb1 Co-authored-by: MichalStrehovsky <13110571+MichalStrehovsky@users.noreply.github.com> --- docs/design/features/typemap.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/design/features/typemap.md b/docs/design/features/typemap.md index 9e1e7572491625..cf7a918fb740a7 100644 --- a/docs/design/features/typemap.md +++ b/docs/design/features/typemap.md @@ -200,6 +200,8 @@ An entry in an External Type Map is included when the "trim target" type is refe Many of these instructions can be passed a generic parameter. In that case, the trimming tool should consider type arguments of instantiations of that type as having met one of these rules and include any entries with those types as "trim target" types. +For pointer, byref, and array types as trim targets, the entries are preserved if the element type meets the requirements above to keep the entry. A trimming tool is free to remove entries if it can prove that the pointer, byref, or array type cannot be represented at runtime. + ### Proxy Type Map An entry in the Proxy Type Map is included when the "source type" is referenced in one of the following ways: @@ -220,3 +222,5 @@ If the type is an interface type and the user could possibly see a `RuntimeTypeH - The owning type of the method argument to `callvirt`, or `ldvirtftn`. Finally, if the trimming tool determines that it is impossible to retrieve a `System.Type` instance the represents the "source type" at runtime, then the entry may be omitted from the Proxy Type Map as its existence is unobservable. + +For pointer, byref, and array types as source types, the entries are preserved if the element type meets the requirements above to keep the entry. A trimming tool is free to remove entries if it can prove that the pointer, byref, or array type cannot be represented at runtime. From 6649805fdca3175a8cf60933485ed3df3b15b9b2 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 22 Apr 2026 22:18:36 +0000 Subject: [PATCH 8/9] Add pointer-element SzArray element-reachability fallback to close spec gap Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/65affbd5-6713-43cd-8c49-619b46bc602f Co-authored-by: MichalStrehovsky <13110571+MichalStrehovsky@users.noreply.github.com> --- .../DependencyAnalysis/ExternalTypeMapNode.cs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ExternalTypeMapNode.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ExternalTypeMapNode.cs index 28dd540f9ff965..17cb937b047f89 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ExternalTypeMapNode.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ExternalTypeMapNode.cs @@ -79,6 +79,17 @@ public override IEnumerable GetConditionalStaticDep context.NecessaryTypeSymbol(effectiveElementType), "MdArray can be constructed at runtime from element type"); } + + // Pointer/function-pointer element SZ arrays can be constructed at runtime from + // just the element type's MethodTable using the hardcoded typeof(char*[]) template. + // If the (underlying) element type is reachable, consider the array reachable as well. + if (arrayType.IsSzArray && (arrayType.ElementType.IsPointer || arrayType.ElementType.IsFunctionPointer)) + { + yield return new CombinedDependencyListEntry( + context.NecessaryTypeSymbol(effectiveTrimTargetType), + context.NecessaryTypeSymbol(effectiveElementType), + "Pointer-element SZ array can be constructed at runtime from element type"); + } } } } From ed7d0f6dbd67da90ba54331754104ab70588b133 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 23 Apr 2026 00:59:36 +0000 Subject: [PATCH 9/9] Refactor array template fallbacks to use IsArrayTypeEligibleForTemplate Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/fe2d5e2f-5153-4dc3-b373-b22c46ccc7ba Co-authored-by: MichalStrehovsky <13110571+MichalStrehovsky@users.noreply.github.com> --- .../DependencyAnalysis/ExternalTypeMapNode.cs | 22 +++++-------------- 1 file changed, 6 insertions(+), 16 deletions(-) diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ExternalTypeMapNode.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ExternalTypeMapNode.cs index 17cb937b047f89..0a15fdc18cabbb 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ExternalTypeMapNode.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ExternalTypeMapNode.cs @@ -69,26 +69,16 @@ public override IEnumerable GetConditionalStaticDep "External type map array trim target with template-loadable element type"); } - // Multidimensional arrays can be constructed at runtime from just the element type's - // MethodTable (e.g. via shared generic code that creates T[,]). If the element type - // is reachable, consider the MdArray type reachable as well. - if (!arrayType.IsSzArray) + // Array types that aren't eligible for templates (MdArrays, pointer/fnptr-element SzArrays) + // can be constructed at runtime from just the element type's MethodTable using hardcoded + // templates (typeof(object[,]) for MdArrays, typeof(char*[]) for pointer arrays). + // If the element type is reachable, consider the array type reachable as well. + if (!GenericTypesTemplateMap.IsArrayTypeEligibleForTemplate(arrayType)) { yield return new CombinedDependencyListEntry( context.NecessaryTypeSymbol(effectiveTrimTargetType), context.NecessaryTypeSymbol(effectiveElementType), - "MdArray can be constructed at runtime from element type"); - } - - // Pointer/function-pointer element SZ arrays can be constructed at runtime from - // just the element type's MethodTable using the hardcoded typeof(char*[]) template. - // If the (underlying) element type is reachable, consider the array reachable as well. - if (arrayType.IsSzArray && (arrayType.ElementType.IsPointer || arrayType.ElementType.IsFunctionPointer)) - { - yield return new CombinedDependencyListEntry( - context.NecessaryTypeSymbol(effectiveTrimTargetType), - context.NecessaryTypeSymbol(effectiveElementType), - "Pointer-element SZ array can be constructed at runtime from element type"); + "Array without template can be constructed at runtime from element type"); } } }