Skip to content

Refactor C# DIM resolution to match C++ structure and fix static virtual dispatch#125205

Merged
MichalStrehovsky merged 10 commits intodotnet:mainfrom
MichalStrehovsky:refactor-dim-resolution-match-cpp
Mar 9, 2026
Merged

Refactor C# DIM resolution to match C++ structure and fix static virtual dispatch#125205
MichalStrehovsky merged 10 commits intodotnet:mainfrom
MichalStrehovsky:refactor-dim-resolution-match-cpp

Conversation

@MichalStrehovsky
Copy link
Member

@MichalStrehovsky MichalStrehovsky commented Mar 5, 2026

Supersedes #121167.

Fixes #88690.

This PR refactors the C# default interface method (DIM) resolution in NativeAOT's type system to match the C++ runtime implementation structure, fixes bugs in static virtual method dispatch, and adds comprehensive test coverage.

Changes

DIM resolution refactoring

  • Unified variant and non-variant DIM resolution into a single FindDefaultInterfaceImplementation worker method with an allowVariance flag, matching the C++ MethodTable::FindDefaultInterfaceImplementation structure
  • Extracted TryGetCandidateImplementation helper matching the C++ equivalent
  • Aligned variable names and comments with the C++ implementation

Static virtual method dispatch fixes

  • Fixed TryResolveConstraintMethodApprox to handle static virtuals on reference types (classes), not just value types
  • Reordered the static virtual check before the value type check to match C++ TryResolveConstraintMethodApprox
  • Replaced IsCanonicalDefinitionType guard with proper canonicalEquivalentFound check matching C++ ResolveVirtualStaticMethod
  • Relaxed Scanner and RyuJIT assertions from IsValueType to IsValueType || Signature.IsStatic
  • Cleaned up dead isStaticVirtualMethod code after the early return restructuring

ILC Scanner crash fix

  • Fixed crash in ConstrainedMethodUseLookupResult.NonRelocDependenciesFromUsage for interfaces with only generic virtual methods (GVMs)
  • Swapped check order so HasInstantiation is evaluated before factory.VTable() to avoid calling ScannedVTableProvider.GetSlice for interfaces whose VTable was never scanned (GVMs are excluded from VTable slots)

New test coverage

  • StaticVirtualOnReferenceType: static virtual on classes via shared generic constrained calls
  • GenericStaticVirtualMethod: method-level generic type parameters
  • CanonicalEquivalentFound: canonical equivalence in shared code
  • StaticVirtualDIMOnReferenceType: default interface methods on reference types

All 646 Loader tests pass (up from 642).

MichalStrehovsky and others added 8 commits February 27, 2026 17:17
Unify ResolveInterfaceMethodToDefaultImplementationOnType and
ResolveVariantInterfaceMethodToDefaultImplementationOnType into a single
FindDefaultInterfaceImplementation worker with an allowVariance parameter,
matching the C++ FindDefaultInterfaceImplementation / TryGetCandidateImplementation
structure in methodtable.cpp.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add an early-return path for static virtual methods matching the C++ implementation in MethodTable::TryResolveConstraintMethodApprox. The C++ handles static virtuals separately by calling ResolveVirtualStaticMethod, and only does so when neither the interface method nor the interface type are shared by generic instantiations.

The C# now mirrors this: when the method, interface type, or constrained type involves canonical forms, resolution is skipped. Otherwise, the method is resolved via ResolveVariantInterfaceMethodToStaticVirtualMethodOnType with fallback to ResolveVariantInterfaceMethodToDefaultImplementationOnType.

This fixes a pre-existing bug where variant static virtual default interface method dispatch was not properly resolved in the NativeAOT compiler.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
copilot --resume=98a1ac45-917c-4269-bff4-fb71fa3c8f35
Rename local variables in FindDefaultInterfaceImplementation and
TryGetCandidateImplementation to match the C++ implementation:
- runtimeInterface -> currentMT (pCurMT)
- interfaceMethodOwningType -> interfaceMT (pInterfaceMT)
- candidateMethod -> candidateMD/currentMD (candidateMD/pCurMD)
- bestCandidateInterface -> bestCandidateMT (pBestCandidateMT)
- bestCandidateMethod -> bestCandidateMD (pBestCandidateMD)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
… check

Replace the over-conservative constrainedType.IsCanonicalSubtype guard with
the canonicalEquivalentFound check from C++ ResolveVirtualStaticMethod. This
matches the C++ behavior: only bail out when the canonical form of the
interface is also in the constrained type's interface map (indicating
ambiguous resolution in shared generic code).

Relax Scanner and RyuJIT assertions to allow static method results on
non-value types, since constrained static virtual calls can resolve to
methods on reference types (e.g. GenericClass<T> implementing an interface).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Move the static virtual method check before the value type check,
matching the C++ ordering in MethodTable::TryResolveConstraintMethodApprox:
1. if (IsStatic()) - handle static virtuals first, return early
2. if (!IsValueType()) - then reject non-value-types for instance methods

This removes the IsCanonicalDefinitionType carve-out from the value type
guard since static virtuals are now fully handled before it runs.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Since static virtual methods now return early at the top of
TryResolveConstraintMethodApprox, the remaining isStaticVirtualMethod
checks in the instance method path are unreachable dead code. Remove
them and inline the check at the entry point.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add four new test cases for static virtual method dispatch through
shared generic constrained calls:
- StaticVirtualOnReferenceType: class-based dispatch
- GenericStaticVirtualMethod: method-level generic type parameters
- CanonicalEquivalentFound: canonical equivalence in shared code
- StaticVirtualDIMOnReferenceType: default interface methods on classes

Fix ILC Scanner crash for GVM-only interfaces by swapping the check
order in ConstrainedMethodUseLookupResult.NonRelocDependenciesFromUsage.
The HasInstantiation check is now evaluated before factory.VTable() to
avoid calling ScannedVTableProvider.GetSlice for interfaces whose VTable
was never scanned (because GVMs are excluded from VTable slots and
VirtualMethodUse is not reported for them).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR updates NativeAOT’s type system and compilation pipeline to align C# default interface method (DIM) resolution and static virtual dispatch behavior with the C++ runtime algorithms, while also adding loader regression coverage for the affected scenarios.

Changes:

  • Refactored DIM resolution in the type system to a unified worker (FindDefaultInterfaceImplementation) with a variance flag and a shared candidate-finding helper.
  • Fixed/adjusted static virtual constrained-call resolution (including canonical-equivalence handling) and relaxed related Scanner/RyuJIT assertions.
  • Added multiple Loader regression tests covering static virtual dispatch on reference types, generic static virtuals, canonical equivalence, and static-virtual DIMs.

Reviewed changes

Copilot reviewed 15 out of 15 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
src/tests/Loader/classloader/StaticVirtualMethods/Regression/StaticVirtualOnReferenceType.csproj Adds new loader test project for static virtual dispatch on reference types.
src/tests/Loader/classloader/StaticVirtualMethods/Regression/StaticVirtualOnReferenceType.cs New regression test covering static virtual constrained calls on classes and structs.
src/tests/Loader/classloader/StaticVirtualMethods/Regression/StaticVirtualDIMOnReferenceType.csproj Adds new loader test project for static-virtual DIM scenarios on reference types.
src/tests/Loader/classloader/StaticVirtualMethods/Regression/StaticVirtualDIMOnReferenceType.cs New regression test combining static virtual dispatch with DIM resolution on classes/structs and generics.
src/tests/Loader/classloader/StaticVirtualMethods/Regression/SimpleStaticVirtual.csproj Adds/updates a simple static-virtual regression test project.
src/tests/Loader/classloader/StaticVirtualMethods/Regression/SimpleStaticVirtual.cs Regression test for static virtual default implementations including variance.
src/tests/Loader/classloader/StaticVirtualMethods/Regression/GenericStaticVirtualMethod.csproj Adds new loader test project for generic static virtual methods.
src/tests/Loader/classloader/StaticVirtualMethods/Regression/GenericStaticVirtualMethod.cs New regression test for method-generic static virtual interface members.
src/tests/Loader/classloader/StaticVirtualMethods/Regression/CanonicalEquivalentFound.csproj Adds new loader test project for canonical-equivalence scenarios.
src/tests/Loader/classloader/StaticVirtualMethods/Regression/CanonicalEquivalentFound.cs New regression test exercising canonical-equivalence handling in static virtual resolution.
src/coreclr/tools/aot/ILCompiler.RyuJit/JitInterface/CorInfoImpl.RyuJit.cs Relaxes an assertion to allow static methods (not only value types) in canonical-resolution paths.
src/coreclr/tools/aot/ILCompiler.Compiler/IL/ILImporter.Scanner.cs Relaxes an assertion similarly in the IL scanner canonical-resolution path.
src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/GenericLookupResult.cs Fixes lookup dependency enumeration to avoid touching VTable state for GVM-only interface cases.
src/coreclr/tools/Common/TypeSystem/Common/MetadataVirtualMethodAlgorithm.cs Refactors DIM resolution to a unified implementation structure aligned with the C++ runtime.
src/coreclr/tools/Common/Compiler/TypeExtensions.cs Refactors/fixes constrained-call resolution to properly handle static virtual dispatch (including reference types) and canonical-equivalence rules.

You can also share your feedback on Copilot code review. Take the survey.

@MichalStrehovsky
Copy link
Member Author

/azp run runtime-nativeaot-outerloop

@azure-pipelines
Copy link

Azure Pipelines successfully started running 1 pipeline(s).

Copy link
Member

@davidwrighton davidwrighton left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I found one issue. Please add a test to see if its actually something, or if I am just seeing things.

MichalStrehovsky and others added 2 commits March 6, 2026 20:12
The C++ FindDefaultInterfaceImplementation walks interfaces in
derived-to-base order using IterateInterfaceMapFrom(dwParentInterfaces).
Match this ordering in the C# implementation by partitioning
RuntimeInterfaces by base type level and iterating from most-derived
to least-derived.

Add TypeSystem unit test validating variant instance DIM resolution
picks the most-derived type's interface first. Enable
DefaultImplementationsOfInterfaces in CoreTestAssembly's RuntimeFeature
to support DIM test types.

Co-Authored-By: Copilot <223556219+Copilot@users.noreply.github.com>
…on NativeAOT

- Add third pass in DispatchResolve for variant default interface method dispatch
- Filter inherited DIM entries in InterfaceDispatchMapNode to avoid redundant
  dispatch map entries that would cause incorrect resolution order
- Add VariantDIMIterationOrder regression test
- Remove ActiveIssue for NativeAOT on ComplexHierarchyPositive (dotnet#88690) and add
  dynamic dispatch tests via reflection

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@MichalStrehovsky
Copy link
Member Author

/azp run runtime-nativeaot-outerloop

@azure-pipelines
Copy link

Azure Pipelines successfully started running 1 pipeline(s).

@MichalStrehovsky
Copy link
Member Author

/ba-g timeouts are unrelated

@MichalStrehovsky MichalStrehovsky merged commit cd38a58 into dotnet:main Mar 9, 2026
132 of 136 checks passed
@MichalStrehovsky MichalStrehovsky deleted the refactor-dim-resolution-match-cpp branch March 9, 2026 00:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Variant static interface dispatch behaves incorrectly on Native AOT

3 participants