NativeAOT: make external TypeMap trimming array-aware with MdArray element fallback#127135
NativeAOT: make external TypeMap trimming array-aware with MdArray element fallback#127135
Conversation
Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/b403556c-8176-43d3-a272-e0f2dd338c46 Co-authored-by: MichalStrehovsky <13110571+MichalStrehovsky@users.noreply.github.com>
Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/b403556c-8176-43d3-a272-e0f2dd338c46 Co-authored-by: MichalStrehovsky <13110571+MichalStrehovsky@users.noreply.github.com>
|
@copilot in the latest commit I added a new test. it fails with: Should the test succeed? If so, fix the bug in the compiler |
There was a problem hiding this comment.
Pull request overview
This PR improves NativeAOT’s external TypeMap trimming precision for array trim targets by conditioning map entries on the array type’s reachability (and, for certain arrays, on template-based runtime constructibility), and extends the NativeAOT trimming smoke test coverage.
Changes:
- Update
ExternalTypeMapNode.GetEffectiveTrimTargetTypeto preserve array types (only unwrap pointer/byref), so TypeMap conditions track array existence rather than collapsing to the element type. - Add a template-based fallback for arrays that don’t have array templates (e.g., MD arrays / pointer-element arrays) by conditioning array reachability on a template-loadable canonical element type.
- Extend the
TestInteropMapArrayTrimmingsmoke test with new TypeMap entries and additional reflection-based coverage.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ExternalTypeMapNode.cs |
Keeps arrays as trim targets and adds conditional dependencies to account for runtime-constructible arrays via templates. |
src/tests/nativeaot/SmokeTests/TrimmingBehaviors/DeadCodeElimination.cs |
Adds new TypeMap entries and runtime reachability patterns to validate array-aware TypeMap trimming behavior. |
| [assembly: TypeMap<DeadCodeElimination.TestInteropMapArrayTrimming>("E", typeof(DeadCodeElimination.TestInteropMapArrayTrimming.Target5), typeof(DeadCodeElimination.TestInteropMapArrayTrimming.TrimTarget5[]))] | ||
| [assembly: TypeMap<DeadCodeElimination.TestInteropMapArrayTrimming>("F", typeof(DeadCodeElimination.TestInteropMapArrayTrimming.Target6), typeof(DeadCodeElimination.TestInteropMapArrayTrimming.TrimTarget6[]))] | ||
| [assembly: TypeMap<DeadCodeElimination.TestInteropMapArrayTrimming>("G", typeof(DeadCodeElimination.TestInteropMapArrayTrimming.Target7), typeof(DeadCodeElimination.TestInteropMapArrayTrimming.TrimTarget7[]))] | ||
| [assembly: TypeMap<DeadCodeElimination.TestInteropMapArrayTrimming>("H", typeof(DeadCodeElimination.TestInteropMapArrayTrimming.Target8), typeof(DeadCodeElimination.TestInteropMapArrayTrimming.TrimTarget8[]))] | ||
| [assembly: TypeMap<DeadCodeElimination.TestInteropMapArrayTrimming>("I", typeof(DeadCodeElimination.TestInteropMapArrayTrimming.Target9), typeof(DeadCodeElimination.TestInteropMapArrayTrimming.TrimTarget9[,]))] |
There was a problem hiding this comment.
The PR description mentions adding only the new "H" regression case in TestInteropMapArrayTrimming, but this diff also adds a new TypeMap entry "I" (TrimTarget9[,] -> Target9) and additional reflection-based helpers to exercise it. Please either update the PR description to cover the new "I" scenario (and why it's needed), or drop the extra test additions if they’re not intended for this change.
| // 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 |
There was a problem hiding this comment.
The new comment says entry "I" is expected because the trim target can be constructed at runtime using the type loader, but the trim target is TrimTarget9[,] where TrimTarget9 is a non-generic DefType. In ExternalTypeMapNode, the new template-based fallback only fires when the (de-parameterized) element type has a canonical form eligible for templates (i.e., canonElementType != effectiveElementType and IsEligibleToHaveATemplate(canonElementType)), which won’t be true for TrimTarget9. Consider clarifying what runtime-constructibility mechanism this test is validating, or adjust the trim target to one whose element type is actually template-loadable so the test aligns with the intended coverage.
| // 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 | |
| // A, B, C, F, G: trim target element type is reachable — entries must be present. | |
| // I: expected due to the shared-generic instantiation exercised above via TestSharedGeneric, | |
| // not because TrimTarget9[,] can use the template-based type-loader fallback for canonical element types. |
|
Test comment
|
Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/67701700-796d-45b7-9aa8-f035408d84d4 Co-authored-by: MichalStrehovsky <13110571+MichalStrehovsky@users.noreply.github.com>
... Fixed in 2c07962. The issue was that for MdArray types (non-SzArray), the runtime can construct them dynamically from just the element type's MethodTable (e.g., via shared generic code that creates |
@copilot Does this match the logic in |
|
@copilot react to comment #127135 (comment) |
No, the MdArray fallback doesn't match As for gaps: pointer/function-pointer element SzArrays have a similar property — they use the hardcoded |
Description
NativeAOT was treating
SomeType[]trim targets asSomeType, which could keep TypeMap entries when only the element type was reachable. This updates array handling to condition on array type existence (or template-based runtime constructibility), matching NativeAOT's higher precision goals.Compiler change: preserve arrays as trim targets
GetEffectiveTrimTargetTypenow strips only non-array parameterized wrappers (pointer/byref), and keepsArrayTypeintact.NecessaryTypeSymbol(arrayType)instead of collapsing to element type.Compiler change: array template fallback for non-template-eligible arrays
NecessaryTypeSymbol(arrayType)on that element template.Compiler change: MdArray element type reachability fallback
T[,]). Added a conditional dependency: ifNecessaryTypeSymbol(elementType)is marked, markNecessaryTypeSymbol(mdArrayType)as reachable. This ensures TypeMap entries for MdArray trim targets are included when the element type is reachable, since the runtime type loader can create MdArrays on demand.Smoke test update:
TestInteropMapArrayTrimmingH(TrimTarget8[] -> Target8) withTrimTarget8/Target8types. MakesTrimTarget8reachable without constructingTrimTarget8[], and assertsHis absent (validates SzArray precision).I(TrimTarget9[,] -> Target9) added by reviewer: validates that MdArray trim targets are present when the element type is reachable and the array is constructed at runtime via shared generic code (MakeSharedGeneric<TrimTarget9>()→new T[1,1]).Original prompt
Background
PR #126380 (merged) fixed a crash when array types are used as TypeMap trim targets. The fix in
ExternalTypeMapNode.csstrips all parameterized types (arrays, pointers, byrefs) to the element type viaGetEffectiveTrimTargetType, then conditions onNecessaryTypeSymbol(elementType). This matches ILLink's behavior but is suboptimal for Native AOT.Problem
The current implementation is imprecise: if
SomeTypeexists in the program butSomeType[]does not, a TypeMap entry withSomeType[]as the trim target will still be included. Native AOT has the capability to track whether the array type itself exists (via MethodTable /NecessaryTypeSymbolfor the array, or via type loader templates for arrays with generic element types).Requested Change
In
src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ExternalTypeMapNode.cs:Modify
GetEffectiveTrimTargetType: Stop stripping array types (ArrayType). Only strip non-array parameterized types (pointers, byrefs). Array types should be kept as-is so we condition on the array type itself.Update
GetConditionalStaticDependencies: For array types, useNecessaryTypeSymbol(arrayType)as the condition. Additionally, handle template-based runtime construction:TrimTarget1<__Canon>[]), add a conditional dependency: if the template exists → markNecessaryTypeSymbol(arrayType). This is already handled by the existing template check code sinceGenericTypesTemplateMap.IsEligibleToHaveATemplatealready handles arrays viaIsArrayTypeEligibleForTemplate.Foo[,], pointer arrays likeFoo*[]), also check if the element type's canonical form has a template. If so, the runtime can construct the element type dynamically and then create the array from it. Strip parameterized wrappers from the element type (e.g., forFoo*[], get toFoo), check if the canonical form is template-eligible, and add the conditional dependency.GetMarkedEntriesshould also work correctly since it callsGetEffectiveTrimTargetType- same changes apply.Key Insight on Template Eligibility
From
GenericTypesTemplateMap.cs:IsEligibleToHaveATemplatereturns true for types thatHasInstantiationORIsArrayTypeEligibleForTemplateIsArrayTypeEligibleForTemplatereturns true only for SzArray (IsSzArray) whose element type is not a pointer or function pointerTrimTarget1<__Canon>[](SzArray) → eligibleTrimTarget2<__Canon>[,](MdArray) → NOT eligible (needs element type template fallback)TrimTarget3<__Canon>*[](SzArray of pointers) → NOT eligible (needs element type template fallback)Test
Add a test to
src/tests/nativeaot/SmokeTests/TrimmingBehaviors/DeadCodeElimination.csspecifically targeting this optimization. The test should add an entry (e.g., "H") where:is TrimTarget8check ornew TrimTarget8())TrimTarget8[]is never constructedNecessaryTypeSymbol(TrimTarget8[])is never marked, even thoughNecessaryTypeSymbol(TrimTarget8)is marked)Add this to the existing
TestInteropMapArrayTrimmingclass. Add:TypeMapassembly attribute for entry "H" with a newTrimTarget8struct andTarget8classRun()method, useTrimTarget8directly (e.g.,if (GetUnknown() is TrimTarget8) ...) to make the element type reachable without making the array type reachableif (map.TryGetValue("H", out _)) throw new Exception("Unexpected entry H");This test validates that the optimization works: conditioning on the array type (not element type) correctly excludes entries where only the element type is reachable.
Expected Results After Change
For the existing test entries:
TrimTarget1<Atom>[]): SzArray, canonTrimTarget1<__Canon>[]has template → present ✓TrimTarget2<Atom>[,]): MdArray, no array template, but elementTrimTarget2<__Canon>has template → present ✓TrimTarget3<Atom>*[]): SzArray of pointers, no array template, but elementTrimTarget3<__Canon>has template → present ✓TrimTarget4<Atom>[]): SzArray, template forTrimTarget4<__Canon>[]would exist only if referenced in compiled code. MakeGenerics skips TrimTarget4 → absent ✓TrimTarget5[]): Non-generic,NecessaryTypeSymbol(TrimTarget5[])never marked → absent ✓TrimTarget6[]): Non-generic,NecessaryTypeSymbol(TrimTarget6[])marked vianew TrimTarget6[1]in MakeGenerics → present ✓TrimTarget7[]): Non-generic,NecessaryTypeSymbol(TrimTarget7[])marked viais TrimTarget7[]cast → present ✓This pull request was created from Copilot chat.