Uh oh!
There was an error while loading. Please reload this page.
JIT: Always use CallInfo method handle for devirtualization - #123434
Conversation
Tagging subscribers to this area: @JulieLeeMSFT, @dotnet/jit-contrib |
There was a problem hiding this comment.
Pull request overview
This PR fixes a devirtualization regression introduced in #122023 by changing how method handles are obtained during late devirtualization. Instead of extracting the method handle from the GenTree (which could fail for certain indirect virtual calls), the PR stores the method handle in LateDevirtualizationInfo during import and retrieves it from there during late devirtualization.
Changes:
- Added
methodHndfield toLateDevirtualizationInfostructure to store the method handle - Simplified
IsDevirtualizationCandidateto just check if a call is virtual or generic virtual, removing the method handle extraction logic - Updated late devirtualization to use the stored method handle instead of attempting to extract it from the tree
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| src/coreclr/jit/inline.h | Added methodHnd field to LateDevirtualizationInfo struct to store the method handle for late devirtualization |
| src/coreclr/jit/importercalls.cpp | Populated the new methodHnd field from callInfo->hMethod when creating late devirtualization info |
| src/coreclr/jit/gentree.h | Removed pMethHandle out parameter from IsDevirtualizationCandidate method signature |
| src/coreclr/jit/gentree.cpp | Simplified IsDevirtualizationCandidate to just return `IsVirtual() |
| src/coreclr/jit/fginline.cpp | Changed to retrieve method handle from gtLateDevirtualizationInfo->methodHnd instead of from out parameter, and removed now-unnecessary assertion |
Uh oh!
There was an error while loading. Please reload this page.
This comment was marked as outdated.
This comment was marked as outdated.
@hez2010 this is not a correct way, either remove it, or replace with |
hez2010
commented
Jan 21, 2026
@EgorBot -amd -intel usingSystem.Runtime.CompilerServices;usingSystem.Text;usingBenchmarkDotNet.Attributes;usingBenchmarkDotNet.Running;BenchmarkSwitcher.FromAssembly(typeof(ContainsTrue).Assembly).Run(args);publicclassContainsTrue{privatestring[]_found;privatestring[]_array;privateList<string>_list;[Params(512)]publicintSize;[GlobalSetup(Targets=new[]{nameof(Array)})]publicvoidSetupArray(){_found=ArrayOfUniqueValues(Size);_array=_found.ToArray();}[Benchmark]publicboolArray(){boolresult=default;string[]collection=_array;string[]found=_found;for(inti=0;i<found.Length;i++)result^=collection.Contains(found[i]);returnresult;}[GlobalSetup(Targets=new[]{nameof(ICollection)})]publicvoidSetupList(){_found=ArrayOfUniqueValues(Size);_list=newList<string>(_found);}[Benchmark]publicboolICollection()=>Contains(_list);[MethodImpl(MethodImplOptions.NoInlining)]privateboolContains(ICollection<string>collection){boolresult=default;string[]found=_found;for(inti=0;i<found.Length;i++)result^=collection.Contains(found[i]);returnresult;}privatestaticstring[]ArrayOfUniqueValues(intcount){string[]result=newstring[count];varrandom=newRandom(42);varuniqueValues=newHashSet<string>();while(uniqueValues.Count!=count){stringvalue=GenerateRandomString(random,1,50);if(!uniqueValues.Contains(value))uniqueValues.Add(value);}uniqueValues.CopyTo(result);returnresult;}privatestaticstringGenerateRandomString(Randomrandom,intminLength,intmaxLength){varlength=random.Next(minLength,maxLength);varbuilder=newStringBuilder(length);for(inti=0;i<length;i++){varrangeSelector=random.Next(0,3);if(rangeSelector==0)builder.Append((char)random.Next('a','z'));elseif(rangeSelector==1)builder.Append((char)random.Next('A','Z'));elsebuilder.Append((char)random.Next('0','9'));}returnbuilder.ToString();}} |
Can confirm this fixes the regression seen in #123391
|
hez2010
commented
Jan 21, 2026
Test failures seem unrelated. |
jakobbotsch
commented
Jan 22, 2026
We have some virtual calls that are implemented as calli in the JIT despite they actually have a method handle. When importing those methods, we didn't actually use the This PR unifies what we use for devirtualization, now we always have the method handle as long as the method is a virtual method. |
jakobbotsch
commented
Jan 22, 2026
Hmm, ok. |
Uh oh!
There was an error while loading. Please reload this page.
…123664) ## Description JIT hits assertion `'!"Unexpected well known arg to method GDV candidate"'` when Guarded Devirtualization encounters methods with `AsyncContinuation` arguments under tiered PGO with delegate/vtable profiling enabled. ## Changes Added `WellKnownArg::AsyncContinuation` to the signature compatibility check in `isCompatibleMethodGDV`: ```cpp case WellKnownArg::RetBuffer: case WellKnownArg::ThisPointer: case WellKnownArg::AsyncContinuation: // Added // Not part of signature but we still expect to see it here continue; ``` Like `RetBuffer` and `ThisPointer`, async continuations are compiler-inserted arguments not reflected in the IL signature. The compatibility check must skip them when matching call arguments against the target method signature. Consistent with existing handling in `fginline.cpp` and `importer.cpp`. <!-- START COPILOT ORIGINAL PROMPT --> <details> <summary>Original prompt</summary> > > ---- > > *This section details on the original issue you should resolve* > > <issue_title>Assertion failed '!"Unexpected well known arg to method GDV candidate"</issue_title> > <issue_description>https://dev.azure.com/dnceng-public/public/_build/results?buildId=1266373&view=ms.vss-test-web.build-test-results-tab&runId=35337768&resultId=209882&paneView=dotnet-dnceng.dnceng-build-release-tasks.helix-test-information-tab > > windows x64 fullpgo method profiling > > ``` > set DOTNET_TieredCompilation=1 > set DOTNET_ReadyToRun=0 > set DOTNET_TC_QuickJitForLoops=1 > set DOTNET_TieredPGO=1 > set DOTNET_JitDelegateProfiling=1 > set DOTNET_JitVTableProfiling=1 > > Discovering: System.Text.Json.Tests (method display = ClassAndMethod, method display options = None) > Discovered: System.Text.Json.Tests (found 10198 of 10271 test cases) > Starting: System.Text.Json.Tests (parallel test collections = on [4 threads], stop on fail = off) > > Assert failure(PID 44644 [0x0000ae64], Thread: 22492 [0x57dc]): Assertion failed '!"Unexpected well known arg to method GDV candidate"' in 'JsonSerializerApiValidation:<SerializeNullException>b__9_1():this' during 'Importation' (IL size 25; hash 0xed53ad00; Tier1) > > File: D:\a\_work\1\s\src\coreclr\jit\importercalls.cpp:7446 > Image: C:\h\w\AB6F09F9\p\dotnet.exe > > ```</issue_description> > > <agent_instructions>Add `WellKnownArg::AsyncContinuation` to the switch in `isCompatibleMethodGDV`</agent_instructions> > > ## Comments on the Issue (you are @copilot in this section) > > <comments> > <comment_new><author>@AndyAyersMS</author><body> > Also several failures in net11.0-windows-Release-x64-fullpgo_random_gdv_methodprofiling_only-Windows.10.Amd64.Open > > ``` > DOTNET_JitClassProfiling=0 > DOTNET_JitDelegateProfiling=1 > DOTNET_JitRandomGuardedDevirtualization=1 > DOTNET_JitRandomlyCollect64BitCounts=1 > DOTNET_JitVTableProfiling=1 > DOTNET_ReadyToRun=0 > DOTNET_TC_QuickJitForLoops=1 > DOTNET_TieredCompilation=1 > DOTNET_TieredPGO=1 > > Discovering: System.IO.Compression.Brotli.Tests (method display = ClassAndMethod, method display options = None) > Discovered: System.IO.Compression.Brotli.Tests (found 109 of 120 test cases) > Starting: System.IO.Compression.Brotli.Tests (parallel test collections = on [4 threads], stop on fail = off) > > Assert failure(PID 16680 [0x00004128], Thread: 14112 [0x3720]): Assertion failed '!"Unexpected well known arg to method GDV candidate"' in 'System.IO.Compression.CompressionStreamUnitTestBase:RoundTripWithZLibCompressionOptions(System.String,System.IO.Compression.ZLibCompressionOptions):this' during 'Importation' (IL size 176; hash 0x03f009b8; Tier1-OSR) > > File: D:\a\_work\1\s\src\coreclr\jit\importercalls.cpp:7446 > ``` > > Maybe related to #123434? -- fyi @hez2010 </body></comment_new> > </comments> > </details> <!-- START COPILOT CODING AGENT SUFFIX --> - Fixes#123653 <!-- START COPILOT CODING AGENT TIPS --> --- 💬 We'd love your input! Share your thoughts on Copilot coding agent in our [2 minute survey](https://gh.io/copilot-coding-agent-survey). --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: jakobbotsch <7887810+jakobbotsch@users.noreply.github.com>
We already have the method handle in CallInfo, so we don't need to rely on the GenTree to extract the method handle for devirtualization.
Previously in #122023 we changed the JIT to only devirtualize a call when we can get the method handle from the GenTree, which was unnecessarily conservative especially when we already have the method handle in CallInfo.
Fixes#123391