From 333a3ad7d1569b8488deac12eea619db28ded5ef Mon Sep 17 00:00:00 2001 From: Andy Gocke Date: Mon, 27 Jul 2026 20:58:47 +0000 Subject: [PATCH 1/2] Do not allow generic inlining of ldftn requiring a fat function pointer When `ldftn` targets a method that needs a hidden generic context argument, ILC asks the JIT to build a fat function pointer and creates a `MethodEntry` generic lookup helper for it. Unlike every other lookup path, this one did not check whether the token came from an inlinee's generic context, so the runtime determined method (expressed in the inlinee's generic parameters) ended up recorded in the inliner's dictionary. Instantiating those dependencies then threw `IndexOutOfRangeException` in `GetNonRuntimeDeterminedTypeFromRuntimeDeterminedSubtypeViaSubstitution`, or silently produced a lookup against the wrong dictionary. Abort the inline in that case, the same way we do for delegate creation (#102299) and for `ComputeLookup`. Fixes #129093. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 4a16e444-be00-4ff2-a37a-f3c3ea4eef4e --- .../JitInterface/CorInfoImpl.RyuJit.cs | 27 +++++++++++++------ .../SmokeTests/UnitTests/Generics.cs | 27 +++++++++++++++++++ 2 files changed, 46 insertions(+), 8 deletions(-) diff --git a/src/coreclr/tools/aot/ILCompiler.RyuJit/JitInterface/CorInfoImpl.RyuJit.cs b/src/coreclr/tools/aot/ILCompiler.RyuJit/JitInterface/CorInfoImpl.RyuJit.cs index 7dc65dc4f5ad8c..14f0f9b6c1e33a 100644 --- a/src/coreclr/tools/aot/ILCompiler.RyuJit/JitInterface/CorInfoImpl.RyuJit.cs +++ b/src/coreclr/tools/aot/ILCompiler.RyuJit/JitInterface/CorInfoImpl.RyuJit.cs @@ -1450,15 +1450,26 @@ private void getCallInfo(ref CORINFO_RESOLVED_TOKEN pResolvedToken, CORINFO_RESO if (pResult->exactContextNeedsRuntimeLookup) { - MethodDesc caller = HandleToObject(callerHandle); - pResult->codePointerOrStubLookup.lookupKind.needsRuntimeLookup = true; - pResult->codePointerOrStubLookup.runtimeLookup.indirections = CORINFO.USEHELPER; - pResult->codePointerOrStubLookup.runtimeLookup.helper = CorInfoHelpFunc.CORINFO_HELP_READYTORUN_GENERIC_HANDLE; - pResult->codePointerOrStubLookup.lookupKind.runtimeLookupKind = GetGenericRuntimeLookupKind(caller); - object helperArg = GetRuntimeDeterminedObjectForToken(ref pResolvedToken); - ISymbolNode helper = GetGenericLookupHelper(pResult->codePointerOrStubLookup.lookupKind.runtimeLookupKind, ReadyToRunHelperId.MethodEntry, caller, helperArg); - pResult->codePointerOrStubLookup.runtimeLookup.helperEntryPoint = CreateConstLookupToSymbol(helper); + + // If this is from a different context, abort. The ReadyToRun helper needs to declare + // its dependencies in terms of the dictionary it will be placed in, but the runtime + // determined method we computed is expressed in terms of the inlinee's generic context. + if (pResolvedToken.tokenContext != contextFromMethodBeingCompiled()) + { + pResult->codePointerOrStubLookup.lookupKind.runtimeLookupKind = CORINFO_RUNTIME_LOOKUP_KIND.CORINFO_LOOKUP_NOT_SUPPORTED; + } + else + { + MethodDesc caller = HandleToObject(callerHandle); + + pResult->codePointerOrStubLookup.runtimeLookup.indirections = CORINFO.USEHELPER; + pResult->codePointerOrStubLookup.runtimeLookup.helper = CorInfoHelpFunc.CORINFO_HELP_READYTORUN_GENERIC_HANDLE; + pResult->codePointerOrStubLookup.lookupKind.runtimeLookupKind = GetGenericRuntimeLookupKind(caller); + object helperArg = GetRuntimeDeterminedObjectForToken(ref pResolvedToken); + ISymbolNode helper = GetGenericLookupHelper(pResult->codePointerOrStubLookup.lookupKind.runtimeLookupKind, ReadyToRunHelperId.MethodEntry, caller, helperArg); + pResult->codePointerOrStubLookup.runtimeLookup.helperEntryPoint = CreateConstLookupToSymbol(helper); + } } else { diff --git a/src/tests/nativeaot/SmokeTests/UnitTests/Generics.cs b/src/tests/nativeaot/SmokeTests/UnitTests/Generics.cs index 8eca170739f971..d3851c849c3dca 100644 --- a/src/tests/nativeaot/SmokeTests/UnitTests/Generics.cs +++ b/src/tests/nativeaot/SmokeTests/UnitTests/Generics.cs @@ -64,6 +64,7 @@ internal static int Run() TestGenericInliningTypeGenericsOnly.Run(); Test99198Regression.Run(); Test102259Regression.Run(); + Test129093Regression.Run(); Test104913Regression.Run(); Test105397Regression.Run(); Test105880Regression.Run(); @@ -3790,6 +3791,32 @@ public static void Run() } } + class Test129093Regression + { + class Gen + { + [MethodImpl(MethodImplOptions.NoInlining)] + public static Type Method() => typeof(T); + } + + // Inlineable generic method taking the address of a method that needs a generic context. + static unsafe nint GetPtr() => (nint)(delegate*)&Gen.Method; + + class Caller + { + [MethodImpl(MethodImplOptions.NoInlining)] + public static nint Run() => GetPtr(); + } + + public static unsafe void Run() + { + if (((delegate*)Caller.Run())() != typeof(object)) + throw new Exception(); + if (((delegate*)Caller.Run())() != typeof(string)) + throw new Exception(); + } + } + class Test104913Regression { interface IFoo From 100dd153b3ee0a0977bfcb4faeb199df419a3a52 Mon Sep 17 00:00:00 2001 From: Andy Gocke Date: Mon, 27 Jul 2026 14:21:48 -0700 Subject: [PATCH 2/2] Apply batched suggestions from code review Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- src/tests/nativeaot/SmokeTests/UnitTests/Generics.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/tests/nativeaot/SmokeTests/UnitTests/Generics.cs b/src/tests/nativeaot/SmokeTests/UnitTests/Generics.cs index d3851c849c3dca..6febd238358128 100644 --- a/src/tests/nativeaot/SmokeTests/UnitTests/Generics.cs +++ b/src/tests/nativeaot/SmokeTests/UnitTests/Generics.cs @@ -3800,6 +3800,7 @@ class Gen } // Inlineable generic method taking the address of a method that needs a generic context. + [MethodImpl(MethodImplOptions.AggressiveInlining)] static unsafe nint GetPtr() => (nint)(delegate*)&Gen.Method; class Caller