From a45a60b02ef21cdf438d301c062183b6feab75c6 Mon Sep 17 00:00:00 2001 From: Vlad Brezae Date: Thu, 2 Apr 2026 12:15:08 +0300 Subject: [PATCH 1/2] [r2r] Include generic type instantiations from GenericLookupSignature for discovery of virtual methods Normally, we detect the usage of a type in the application via the TypeFixupSignature and then we use this type to determine which virtual methods on the type need compilation (via supporting GVMDependenciesNode and VirtualMethodUseNode). Consider the following scenario: ``` public class TestA { public virtual void TestMethod(T item) => Console.WriteLine($"{item} / {typeof(U)}"); } public class TestB { public TestA TestCreate() => new TestA(); } TestB obj = new TestB(); obj.TestCreate().TestMethod("hello"); ``` When compiling `TestB.TestCreate` we need to create an instance of `TestA`. Because we are compiling a shared version we need to embed a call to a generic lookup helper in order to obtain the actual T. This is done with a GenericLookupSignature fixup, which contains the type `TestA` that needs resolving. When creating this node, we add the InheritedVirtualMethodsNode as a dependency which facilitates discovery of virtual methods on this type. --- .../ReadyToRun/GenericLookupSignature.cs | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/GenericLookupSignature.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/GenericLookupSignature.cs index e97d8cd835b2f2..d30ee028c40c4b 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/GenericLookupSignature.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/GenericLookupSignature.cs @@ -133,10 +133,30 @@ public override ObjectData GetData(NodeFactory factory, bool relocsOnly = false) protected override DependencyList ComputeNonRelocationBasedDependencies(NodeFactory factory) { DependencyList dependencies = null; + if (_fixupKind == ReadyToRunFixupKind.TypeHandle) { TypeFixupSignature.AddDependenciesForAsyncStateMachineBox(ref dependencies, factory, _typeArgument); + + // In shared generic code, newobj uses a generic dictionary lookup for the type handle + // rather than a direct READYTORUN_FIXUP_TypeHandle (TypeFixupSignature). Mirror the + // creation of InheritedVirtualMethodsNode as its done in TypeFixupSignature, so we + // scan the virtual methods on this type for dependency analysis. + if (_typeArgument != null) + { + TypeDesc canonType = _typeArgument.ConvertToCanonForm(CanonicalFormKind.Specific); + if (!canonType.IsGenericDefinition && + !canonType.IsInterface && + canonType.IsDefType && + (factory.CompilationCurrentPhase == 0) && + factory.CompilationModuleGroup.VersionsWithType(canonType)) + { + dependencies ??= new DependencyList(); + dependencies.Add(factory.InheritedVirtualMethods(canonType), "Generic lookup type discovery for virtual dispatch"); + } + } } + return dependencies; } From 39217897ef967a561fda9b27dff0fb067a43b747 Mon Sep 17 00:00:00 2001 From: Vlad Brezae Date: Thu, 25 Jun 2026 22:03:47 +0300 Subject: [PATCH 2/2] Add r2r test --- .../TestCases/R2RTestSuites.cs | 28 ++++++++++++++ .../VirtualMethodGenerics/GenericLookup.cs | 37 +++++++++++++++++++ 2 files changed, 65 insertions(+) create mode 100644 src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCases/VirtualMethodGenerics/GenericLookup.cs diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCases/R2RTestSuites.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCases/R2RTestSuites.cs index 1f545ceb58fe59..b68001cef642f8 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCases/R2RTestSuites.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCases/R2RTestSuites.cs @@ -1546,4 +1546,32 @@ static void Validate(ReadyToRunReader reader) Assert.True(R2RAssert.HasCompiledMethod(reader, "ITest7`1", "ITest7Base.Test7Method", out diag, ["int"]), diag); } } + + [Fact] + public void VirtualMethodGenericsGenericLookup() + { + var genericLookupLib = new CompiledAssembly + { + AssemblyName = nameof(VirtualMethodGenericsGenericLookup), + SourceResourceNames = ["VirtualMethodGenerics/GenericLookup.cs"], + }; + + new R2RTestRunner(_output).Run(new R2RTestCase( + nameof(VirtualMethodGenericsGenericLookup), + [ + new(nameof(VirtualMethodGenericsGenericLookup), [new CrossgenAssembly(genericLookupLib)]) + { + Validate = Validate, + }, + ])); + + static void Validate(ReadyToRunReader reader) + { + string diag; + + // The generic type instantiation is reached only through a GenericLookupSignature + // fixup, so its virtual method must still be discovered and compiled. + Assert.True(R2RAssert.HasCompiledMethod(reader, "TestA`2<__Canon,int>", "TestMethod", out diag), diag); + } + } } diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCases/VirtualMethodGenerics/GenericLookup.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCases/VirtualMethodGenerics/GenericLookup.cs new file mode 100644 index 00000000000000..5c6629c56bd5ec --- /dev/null +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCases/VirtualMethodGenerics/GenericLookup.cs @@ -0,0 +1,37 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Runtime.CompilerServices; + +// Virtual method discovery for a generic type instantiation that is only ever +// reached through a GenericLookupSignature fixup. +// +// When compiling the shared TestB<__Canon, int>.TestCreate, the instance of +// TestA is obtained through a generic dictionary lookup (a +// GenericLookupSignature fixup referencing TestA<__Canon, int>) rather than via +// a TypeFixupSignature. The virtual TestMethod on TestA<__Canon, int> must still +// be discovered and compiled. + +public class TestA +{ + public virtual void TestMethod(T item) => Console.WriteLine($"{item} / {typeof(U)}"); +} + +public class TestB +{ + [MethodImpl(MethodImplOptions.NoInlining)] + public TestA TestCreate() => new TestA(); +} + +static class GenericLookupTests +{ + [MethodImpl(MethodImplOptions.NoInlining)] + static TestB CreateTestB() => new TestB(); + + static void Run() + { + TestB obj = CreateTestB(); + obj.TestCreate().TestMethod("hello"); + } +}