Don't require GDV targets to be inlineable - #132375

Merged
EgorBo merged 5 commits into
dotnet:mainfrom
EgorBo:gdv-no-inline
Sep 1, 2026
Merged

Don't require GDV targets to be inlineable#132375
EgorBo merged 5 commits into
dotnet:mainfrom
EgorBo:gdv-no-inline

Conversation

@EgorBo

@EgorBoEgorBo commented Aug 16, 2026

Copy link
Copy Markdown
Member

GDV used to bail out when the target wasn't inlineable. A direct call still beats a virtual one, so keep the candidate and just don't inline it. DOTNET_JitGuardedDevirtualizationRequireInlining=1 restores the old behavior. Class probes only.

I know it doesn't match Andy's numbers, but many things have changed since then (e.g. CET), also, I assume we never tested it on arm64. So far it's a pure improvement on most kinds of scenarious (interface, virtual calls, etc.), see benchmarks below. We still might want to avoid doing it for virtual calls (esp for large target methods where the cost of the type check + direct call is a noise anyway) only on CoreCLR, but I propose we do it for everything and watch dotnet/performance reports.

Should help NativeAOT the most, where getExactClasses gives exact devirtualization but targets often aren't inlineable (if at least one target of getExactClasses wasn't inlineable we used to bail on the whole thing).

CoreCLR, default env, all targets [NoInlining] (so baseline does no GDV at all). Columns = how often the guessed type hits, ratio = new/base, lower is better.

BenchmarkMonoHit90Hit50Hit33Hit25
DirectControl (noise floor)1.010.991.011.011.00
Interface$\color{green}{0.72}$$\color{green}{0.48}$$\color{green}{0.45}$$\color{green}{0.64}$$\color{green}{0.56}$
InterfaceShared (__Canon)$\color{green}{0.88}$$\color{green}{0.50}$$\color{green}{0.51}$$\color{green}{0.62}$$\color{green}{0.59}$
InterfaceCostlyTarget$\color{green}{0.86}$$\color{green}{0.58}$$\color{green}{0.55}$$\color{green}{0.65}$$\color{green}{0.60}$
Virtual$\color{green}{0.86}$$\color{green}{0.78}$$\color{green}{0.63}$$\color{green}{0.64}$$\color{green}{0.62}$
VirtualShared (__Canon)$\color{red}{1.19}$$\color{green}{0.55}$$\color{green}{0.55}$$\color{green}{0.52}$$\color{green}{0.63}$
InterfaceHugeTarget (GDV in both)1.101.101.031.011.00

Only regression is monomorphic shared-generic virtual - a well-predicted vtable call is already as cheap as compare+direct call.

rt-sz report: MichalStrehovsky/rt-sz#246
smoke-test benchmarks: EgorBot/Benchmarks#489 (comment)

Guarded devirtualization used to give up whenever the target we'd devirtualize
to couldn't be inlined. But a direct call is still cheaper than a virtual or
interface call, so keep the candidate and just don't inline it.
Added JitGuardedDevirtualizationRequireInlining to get the old behavior back.
Class-based GDV only for now, method/delegate GDV is unchanged.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 38498b8d-c116-4637-b069-d1d275634193
CopilotAI lite review requested due to automatic review settings August 16, 2026 16:31
@github-actionsgithub-actionsBot added the area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI label Aug 16, 2026
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 5 pipeline(s).
11 pipeline(s) were filtered out due to trigger conditions.
There may be pipelines that require an authorized user to comment /azp run to run.

@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @JulieLeeMSFT, @jakobbotsch
See info in area-owners.md if you want to be subscribed.

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

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 adjusts CoreCLR JIT guarded devirtualization (GDV) so that class-based GDV candidates can be kept and expanded even when the devirtualized target isn’t inlineable, preserving the direct-call benefit. It also adds a config switch to restore the legacy “require inlineability” behavior and tightens up candidate bookkeeping to support the new flow.

Changes:

  • Add JitGuardedDevirtualizationRequireInlining config (default 0) to optionally restore legacy behavior of dropping non-inlineable GDV targets.
  • Track per-candidate inlineability via InlineCandidateInfo::isInlineable and keep non-inlineable class-GDV candidates for devirtualization-only.
  • Fix candidate bookkeeping (value-initialize InlineCandidateInfo and adjust spill-temp/enumerator-cloning mapping handling) to avoid relying on impCheckCanInline having run.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 1 comment.

Show a summary per file
FileDescription
src/coreclr/jit/jitmetadatalist.hAdds a new JIT metric counter (NoInlineGDV) for devirt-without-inlining cases.
src/coreclr/jit/jitconfigvalues.hIntroduces JitGuardedDevirtualizationRequireInlining config to gate legacy behavior.
src/coreclr/jit/inline.hAdds InlineCandidateInfo::isInlineable to distinguish inline vs devirt-only GDV candidates.
src/coreclr/jit/indirectcalltransformer.cppRemoves “must be inline-candidate” bail-out; expands GDV while conditionally re-marking inline candidates; fixes spill-temp and enumerator cloning map behavior.
src/coreclr/jit/importercalls.cppKeeps non-inlineable class-GDV candidates when legal; value-initializes candidate info; adjusts struct-return fixup deferral for GDV candidates.
src/coreclr/jit/compiler.hDeclares Compiler::canKeepNonInlineableGdvCandidate.

Comment threadsrc/coreclr/jit/importercalls.cpp Outdated
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 38498b8d-c116-4637-b069-d1d275634193
CopilotAI review requested due to automatic review settings August 16, 2026 16:53

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated no new comments.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 38498b8d-c116-4637-b069-d1d275634193
CopilotAI review requested due to automatic review settings August 16, 2026 17:09
@github-actionsgithub-actionsBot mentioned this pull request Aug 16, 2026

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated no new comments.

@MichalPetryka

Copy link
Copy Markdown
Contributor

@MihuBot -nuget

@EgorBo

Copy link
Copy Markdown
MemberAuthor

@MihuBot -nuget

@MichalPetryka This change requires PGO data, MihuBot is basically just PrepareMethod

@EgorBo

Copy link
Copy Markdown
MemberAuthor

/azp run runtime-coreclr pgostress, runtime-coreclr libraries-pgo

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 2 pipeline(s).

@EgorBo

Copy link
Copy Markdown
MemberAuthor

@EgorBot -linux_amd -osx_arm64 -windows_amd

usingSystem.Runtime.CompilerServices;usingBenchmarkDotNet.Attributes;usingBenchmarkDotNet.Running;BenchmarkSwitcher.FromAssembly(typeof(Bench).Assembly).Run(args);publicinterfaceIOp{intDo(intx);}publicclassOpA:IOp{[MethodImpl(MethodImplOptions.NoInlining)]publicintDo(intx)=>x+1;}publicclassOpB:IOp{[MethodImpl(MethodImplOptions.NoInlining)]publicintDo(intx)=>x+2;}publicabstractclassBase{publicabstractintDo(intx);}publicclassDerA:Base{[MethodImpl(MethodImplOptions.NoInlining)]publicoverrideintDo(intx)=>x+1;}publicclassDerB:Base{[MethodImpl(MethodImplOptions.NoInlining)]publicoverrideintDo(intx)=>x+2;}publicclassBench{privateIOp[]_ifaces=newIOp[64];privateBase[]_virts=newBase[64];[GlobalSetup]publicvoidSetup(){for(inti=0;i<64;i++){// 75% A, 25% B_ifaces[i]=i%4==0?newOpB():newOpA();_virts[i]=i%4==0?newDerB():(Base)newDerA();}}[Benchmark]publicintInterfaceCall(){intsum=0;IOp[]ops=_ifaces;for(inti=0;i<ops.Length;i++)sum+=ops[i].Do(i);returnsum;}[Benchmark]publicintVirtualCall(){intsum=0;Base[]objs=_virts;for(inti=0;i<objs.Length;i++)sum+=objs[i].Do(i);returnsum;}}

@EgorBo

Copy link
Copy Markdown
MemberAuthor

PTAL @AndyAyersMS (see PR description), .NET 12.0 change cc @dotnet/jit-contrib.

@EgorBo

EgorBo commented Aug 17, 2026

Copy link
Copy Markdown
MemberAuthor

cc @MichalStrehovsky I think you asked for it a while back for NAOT.

Comment threadsrc/coreclr/jit/importercalls.cpp Outdated
Comment threadsrc/coreclr/jit/importercalls.cpp
EgorBoand others added 2 commits August 26, 2026 18:10
… revoke dump
The tail-recursion-to-loop transform in fgMorphPotentialTailCall requires a
non-virtual callee, so bailing out of GDV for recursive implicit tail calls
didn't preserve the transform - it prevented it. Keep the candidate instead.
Also restore the 'Revoking guarded devirtualization candidate' JITDUMP.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings August 26, 2026 16:30

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated 2 comments.

Comment threadsrc/coreclr/jit/inline.h
Comment threadsrc/coreclr/jit/jitconfigvalues.h
@EgorBo

Copy link
Copy Markdown
MemberAuthor

@AndyAyersMS addressed your feedback, it now needs re-approval 🙂

@EgorBo

Copy link
Copy Markdown
MemberAuthor

Ping @AndyAyersMS

@EgorBo
EgorBo enabled auto-merge (squash) September 1, 2026 11:20
@EgorBo
EgorBo merged commit 0055ffd into dotnet:mainSep 1, 2026
134 checks passed
@BrzVlad

Copy link
Copy Markdown
Member

@EgorBo I suspect this started causing crashes on my PR (#132787). Thoughts ?

 /__w/1/s/artifacts/bin/coreclr/linux.x64.Checked/x64/crossgen2/crossgen2 -o:/__w/1/s/artifacts/bin/coreclr/linux.x64.Checked/System.Private.CoreLib.dll -r:/__w/1/s/artifacts/bin/coreclr/linux.x64.Checked/IL/*.dll --targetarch:x64 --obj-format:pe --targetos:linux --target-allows-runtime-code-generation:true -m:/__w/1/s/artifacts/bin/coreclr/linux.x64.Checked/StandardOptimizationData.mibc --embed-pgo-data -O --verify-type-and-field-layout --enable-cached-interface-dispatch-support /__w/1/s/artifacts/bin/coreclr/linux.x64.Checked/IL/System.Private.CoreLib.dll --perfmap-format-version:1 --perfmap --perfmap-path:/__w/1/s/artifacts/bin/coreclr/linux.x64.Checked/
/__w/1/s/src/coreclr/jit/fgdiagnostic.cpp:3416
Assertion failed 'block->HasFlag(BBF_RECURSIVE_TAILCALL)' in 'System.Type:GetTypeCodeImpl():int:this' during 'Morph - Inlining' (IL size 23; hash 0xf87e7a03; FullOpts)

@EgorBo

EgorBo commented Sep 1, 2026

Copy link
Copy Markdown
MemberAuthor

@EgorBo I suspect this started causing crashes on my PR (#132787). Thoughts ?

 /__w/1/s/artifacts/bin/coreclr/linux.x64.Checked/x64/crossgen2/crossgen2 -o:/__w/1/s/artifacts/bin/coreclr/linux.x64.Checked/System.Private.CoreLib.dll -r:/__w/1/s/artifacts/bin/coreclr/linux.x64.Checked/IL/*.dll --targetarch:x64 --obj-format:pe --targetos:linux --target-allows-runtime-code-generation:true -m:/__w/1/s/artifacts/bin/coreclr/linux.x64.Checked/StandardOptimizationData.mibc --embed-pgo-data -O --verify-type-and-field-layout --enable-cached-interface-dispatch-support /__w/1/s/artifacts/bin/coreclr/linux.x64.Checked/IL/System.Private.CoreLib.dll --perfmap-format-version:1 --perfmap --perfmap-path:/__w/1/s/artifacts/bin/coreclr/linux.x64.Checked/
/__w/1/s/src/coreclr/jit/fgdiagnostic.cpp:3416
Assertion failed 'block->HasFlag(BBF_RECURSIVE_TAILCALL)' in 'System.Type:GetTypeCodeImpl():int:this' during 'Morph - Inlining' (IL size 23; hash 0xf87e7a03; FullOpts)

Yep, sorry, it's a bad last-minute feedback address change, I'll file a fix in a minute

@EgorBo

EgorBo commented Sep 1, 2026

Copy link
Copy Markdown
MemberAuthor

#133014

Fixed.

EgorBo added a commit that referenced this pull request Sep 1, 2026
Main is currently broken in Checked CoreCLR builds while crossgenning
`System.Private.CoreLib`:
```
Assertion failed 'block->HasFlag(BBF_RECURSIVE_TAILCALL)' in 'System.Type:GetTypeCodeImpl():int:this' during 'Morph - Inlining'
```
#132375 began retaining non-inlineable GDV candidates for implicit
recursive tail calls after [this review
discussion](#132375 (comment)).
GDV expansion then moves the residual recursive call into a new block
without its recursive-tailcall block marker, triggering the assertion.
Conservatively avoid retaining non-inlineable GDV candidates for all
tail calls. Inlineable implicit-tail-call GDV remains unchanged. We can
be less conservative, but that requires changes in the GDV expansion
code (at very least - mark blocks as containing a tail call, ensure its
shape is correct).
Validation:
- `build.cmd Clr.Jit -c Checked`
- Re-ran the failing Crossgen2 command
- SuperPMI replay of `benchmarks.run.` (56,322 contexts)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 0458833e-ff11-4cb8-9ca0-8e06f2034c2d
@EgorBo
EgorBo deleted the gdv-no-inline branch September 1, 2026 14:01
@dotnet-milestone-botdotnet-milestone-botBot added this to the 12.0-preview1 milestone Sep 2, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area-CodeGen-coreclrCLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants

@EgorBo@MichalPetryka@BrzVlad@AndyAyersMS
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Don't require GDV targets to be inlineable - #132375

Merged
EgorBo merged 5 commits into
dotnet:mainfrom
EgorBo:gdv-no-inline
Sep 1, 2026
Merged

Don't require GDV targets to be inlineable#132375
EgorBo merged 5 commits into
dotnet:mainfrom
EgorBo:gdv-no-inline

Conversation

@EgorBo

@EgorBoEgorBo commented Aug 16, 2026

Copy link
Copy Markdown
Member

GDV used to bail out when the target wasn't inlineable. A direct call still beats a virtual one, so keep the candidate and just don't inline it. DOTNET_JitGuardedDevirtualizationRequireInlining=1 restores the old behavior. Class probes only.

I know it doesn't match Andy's numbers, but many things have changed since then (e.g. CET), also, I assume we never tested it on arm64. So far it's a pure improvement on most kinds of scenarious (interface, virtual calls, etc.), see benchmarks below. We still might want to avoid doing it for virtual calls (esp for large target methods where the cost of the type check + direct call is a noise anyway) only on CoreCLR, but I propose we do it for everything and watch dotnet/performance reports.

Should help NativeAOT the most, where getExactClasses gives exact devirtualization but targets often aren't inlineable (if at least one target of getExactClasses wasn't inlineable we used to bail on the whole thing).

CoreCLR, default env, all targets [NoInlining] (so baseline does no GDV at all). Columns = how often the guessed type hits, ratio = new/base, lower is better.

BenchmarkMonoHit90Hit50Hit33Hit25
DirectControl (noise floor)1.010.991.011.011.00
Interface$\color{green}{0.72}$$\color{green}{0.48}$$\color{green}{0.45}$$\color{green}{0.64}$$\color{green}{0.56}$
InterfaceShared (__Canon)$\color{green}{0.88}$$\color{green}{0.50}$$\color{green}{0.51}$$\color{green}{0.62}$$\color{green}{0.59}$
InterfaceCostlyTarget$\color{green}{0.86}$$\color{green}{0.58}$$\color{green}{0.55}$$\color{green}{0.65}$$\color{green}{0.60}$
Virtual$\color{green}{0.86}$$\color{green}{0.78}$$\color{green}{0.63}$$\color{green}{0.64}$$\color{green}{0.62}$
VirtualShared (__Canon)$\color{red}{1.19}$$\color{green}{0.55}$$\color{green}{0.55}$$\color{green}{0.52}$$\color{green}{0.63}$
InterfaceHugeTarget (GDV in both)1.101.101.031.011.00

Only regression is monomorphic shared-generic virtual - a well-predicted vtable call is already as cheap as compare+direct call.

rt-sz report: MichalStrehovsky/rt-sz#246
smoke-test benchmarks: EgorBot/Benchmarks#489 (comment)

Guarded devirtualization used to give up whenever the target we'd devirtualize
to couldn't be inlined. But a direct call is still cheaper than a virtual or
interface call, so keep the candidate and just don't inline it.
Added JitGuardedDevirtualizationRequireInlining to get the old behavior back.
Class-based GDV only for now, method/delegate GDV is unchanged.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 38498b8d-c116-4637-b069-d1d275634193
CopilotAI lite review requested due to automatic review settings August 16, 2026 16:31
@github-actionsgithub-actionsBot added the area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI label Aug 16, 2026
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 5 pipeline(s).
11 pipeline(s) were filtered out due to trigger conditions.
There may be pipelines that require an authorized user to comment /azp run to run.

@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @JulieLeeMSFT, @jakobbotsch
See info in area-owners.md if you want to be subscribed.

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

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 adjusts CoreCLR JIT guarded devirtualization (GDV) so that class-based GDV candidates can be kept and expanded even when the devirtualized target isn’t inlineable, preserving the direct-call benefit. It also adds a config switch to restore the legacy “require inlineability” behavior and tightens up candidate bookkeeping to support the new flow.

Changes:

  • Add JitGuardedDevirtualizationRequireInlining config (default 0) to optionally restore legacy behavior of dropping non-inlineable GDV targets.
  • Track per-candidate inlineability via InlineCandidateInfo::isInlineable and keep non-inlineable class-GDV candidates for devirtualization-only.
  • Fix candidate bookkeeping (value-initialize InlineCandidateInfo and adjust spill-temp/enumerator-cloning mapping handling) to avoid relying on impCheckCanInline having run.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 1 comment.

Show a summary per file
FileDescription
src/coreclr/jit/jitmetadatalist.hAdds a new JIT metric counter (NoInlineGDV) for devirt-without-inlining cases.
src/coreclr/jit/jitconfigvalues.hIntroduces JitGuardedDevirtualizationRequireInlining config to gate legacy behavior.
src/coreclr/jit/inline.hAdds InlineCandidateInfo::isInlineable to distinguish inline vs devirt-only GDV candidates.
src/coreclr/jit/indirectcalltransformer.cppRemoves “must be inline-candidate” bail-out; expands GDV while conditionally re-marking inline candidates; fixes spill-temp and enumerator cloning map behavior.
src/coreclr/jit/importercalls.cppKeeps non-inlineable class-GDV candidates when legal; value-initializes candidate info; adjusts struct-return fixup deferral for GDV candidates.
src/coreclr/jit/compiler.hDeclares Compiler::canKeepNonInlineableGdvCandidate.

Comment threadsrc/coreclr/jit/importercalls.cpp Outdated
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 38498b8d-c116-4637-b069-d1d275634193
CopilotAI review requested due to automatic review settings August 16, 2026 16:53

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated no new comments.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 38498b8d-c116-4637-b069-d1d275634193
CopilotAI review requested due to automatic review settings August 16, 2026 17:09
@github-actionsgithub-actionsBot mentioned this pull request Aug 16, 2026

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated no new comments.

@MichalPetryka

Copy link
Copy Markdown
Contributor

@MihuBot -nuget

@EgorBo

Copy link
Copy Markdown
MemberAuthor

@MihuBot -nuget

@MichalPetryka This change requires PGO data, MihuBot is basically just PrepareMethod

@EgorBo

Copy link
Copy Markdown
MemberAuthor

/azp run runtime-coreclr pgostress, runtime-coreclr libraries-pgo

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 2 pipeline(s).

@EgorBo

Copy link
Copy Markdown
MemberAuthor

@EgorBot -linux_amd -osx_arm64 -windows_amd

usingSystem.Runtime.CompilerServices;usingBenchmarkDotNet.Attributes;usingBenchmarkDotNet.Running;BenchmarkSwitcher.FromAssembly(typeof(Bench).Assembly).Run(args);publicinterfaceIOp{intDo(intx);}publicclassOpA:IOp{[MethodImpl(MethodImplOptions.NoInlining)]publicintDo(intx)=>x+1;}publicclassOpB:IOp{[MethodImpl(MethodImplOptions.NoInlining)]publicintDo(intx)=>x+2;}publicabstractclassBase{publicabstractintDo(intx);}publicclassDerA:Base{[MethodImpl(MethodImplOptions.NoInlining)]publicoverrideintDo(intx)=>x+1;}publicclassDerB:Base{[MethodImpl(MethodImplOptions.NoInlining)]publicoverrideintDo(intx)=>x+2;}publicclassBench{privateIOp[]_ifaces=newIOp[64];privateBase[]_virts=newBase[64];[GlobalSetup]publicvoidSetup(){for(inti=0;i<64;i++){// 75% A, 25% B_ifaces[i]=i%4==0?newOpB():newOpA();_virts[i]=i%4==0?newDerB():(Base)newDerA();}}[Benchmark]publicintInterfaceCall(){intsum=0;IOp[]ops=_ifaces;for(inti=0;i<ops.Length;i++)sum+=ops[i].Do(i);returnsum;}[Benchmark]publicintVirtualCall(){intsum=0;Base[]objs=_virts;for(inti=0;i<objs.Length;i++)sum+=objs[i].Do(i);returnsum;}}

@EgorBo

Copy link
Copy Markdown
MemberAuthor

PTAL @AndyAyersMS (see PR description), .NET 12.0 change cc @dotnet/jit-contrib.

@EgorBo

EgorBo commented Aug 17, 2026

Copy link
Copy Markdown
MemberAuthor

cc @MichalStrehovsky I think you asked for it a while back for NAOT.

Comment threadsrc/coreclr/jit/importercalls.cpp Outdated
Comment threadsrc/coreclr/jit/importercalls.cpp
EgorBoand others added 2 commits August 26, 2026 18:10
… revoke dump
The tail-recursion-to-loop transform in fgMorphPotentialTailCall requires a
non-virtual callee, so bailing out of GDV for recursive implicit tail calls
didn't preserve the transform - it prevented it. Keep the candidate instead.
Also restore the 'Revoking guarded devirtualization candidate' JITDUMP.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings August 26, 2026 16:30

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated 2 comments.

Comment threadsrc/coreclr/jit/inline.h
Comment threadsrc/coreclr/jit/jitconfigvalues.h
@EgorBo

Copy link
Copy Markdown
MemberAuthor

@AndyAyersMS addressed your feedback, it now needs re-approval 🙂

@EgorBo

Copy link
Copy Markdown
MemberAuthor

Ping @AndyAyersMS

@EgorBo
EgorBo enabled auto-merge (squash) September 1, 2026 11:20
@EgorBo
EgorBo merged commit 0055ffd into dotnet:mainSep 1, 2026
134 checks passed
@BrzVlad

Copy link
Copy Markdown
Member

@EgorBo I suspect this started causing crashes on my PR (#132787). Thoughts ?

 /__w/1/s/artifacts/bin/coreclr/linux.x64.Checked/x64/crossgen2/crossgen2 -o:/__w/1/s/artifacts/bin/coreclr/linux.x64.Checked/System.Private.CoreLib.dll -r:/__w/1/s/artifacts/bin/coreclr/linux.x64.Checked/IL/*.dll --targetarch:x64 --obj-format:pe --targetos:linux --target-allows-runtime-code-generation:true -m:/__w/1/s/artifacts/bin/coreclr/linux.x64.Checked/StandardOptimizationData.mibc --embed-pgo-data -O --verify-type-and-field-layout --enable-cached-interface-dispatch-support /__w/1/s/artifacts/bin/coreclr/linux.x64.Checked/IL/System.Private.CoreLib.dll --perfmap-format-version:1 --perfmap --perfmap-path:/__w/1/s/artifacts/bin/coreclr/linux.x64.Checked/
/__w/1/s/src/coreclr/jit/fgdiagnostic.cpp:3416
Assertion failed 'block->HasFlag(BBF_RECURSIVE_TAILCALL)' in 'System.Type:GetTypeCodeImpl():int:this' during 'Morph - Inlining' (IL size 23; hash 0xf87e7a03; FullOpts)

@EgorBo

EgorBo commented Sep 1, 2026

Copy link
Copy Markdown
MemberAuthor

@EgorBo I suspect this started causing crashes on my PR (#132787). Thoughts ?

 /__w/1/s/artifacts/bin/coreclr/linux.x64.Checked/x64/crossgen2/crossgen2 -o:/__w/1/s/artifacts/bin/coreclr/linux.x64.Checked/System.Private.CoreLib.dll -r:/__w/1/s/artifacts/bin/coreclr/linux.x64.Checked/IL/*.dll --targetarch:x64 --obj-format:pe --targetos:linux --target-allows-runtime-code-generation:true -m:/__w/1/s/artifacts/bin/coreclr/linux.x64.Checked/StandardOptimizationData.mibc --embed-pgo-data -O --verify-type-and-field-layout --enable-cached-interface-dispatch-support /__w/1/s/artifacts/bin/coreclr/linux.x64.Checked/IL/System.Private.CoreLib.dll --perfmap-format-version:1 --perfmap --perfmap-path:/__w/1/s/artifacts/bin/coreclr/linux.x64.Checked/
/__w/1/s/src/coreclr/jit/fgdiagnostic.cpp:3416
Assertion failed 'block->HasFlag(BBF_RECURSIVE_TAILCALL)' in 'System.Type:GetTypeCodeImpl():int:this' during 'Morph - Inlining' (IL size 23; hash 0xf87e7a03; FullOpts)

Yep, sorry, it's a bad last-minute feedback address change, I'll file a fix in a minute

@EgorBo

EgorBo commented Sep 1, 2026

Copy link
Copy Markdown
MemberAuthor

#133014

Fixed.

EgorBo added a commit that referenced this pull request Sep 1, 2026
Main is currently broken in Checked CoreCLR builds while crossgenning
`System.Private.CoreLib`:
```
Assertion failed 'block->HasFlag(BBF_RECURSIVE_TAILCALL)' in 'System.Type:GetTypeCodeImpl():int:this' during 'Morph - Inlining'
```
#132375 began retaining non-inlineable GDV candidates for implicit
recursive tail calls after [this review
discussion](#132375 (comment)).
GDV expansion then moves the residual recursive call into a new block
without its recursive-tailcall block marker, triggering the assertion.
Conservatively avoid retaining non-inlineable GDV candidates for all
tail calls. Inlineable implicit-tail-call GDV remains unchanged. We can
be less conservative, but that requires changes in the GDV expansion
code (at very least - mark blocks as containing a tail call, ensure its
shape is correct).
Validation:
- `build.cmd Clr.Jit -c Checked`
- Re-ran the failing Crossgen2 command
- SuperPMI replay of `benchmarks.run.` (56,322 contexts)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 0458833e-ff11-4cb8-9ca0-8e06f2034c2d
@EgorBo
EgorBo deleted the gdv-no-inline branch September 1, 2026 14:01
@dotnet-milestone-botdotnet-milestone-botBot added this to the 12.0-preview1 milestone Sep 2, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area-CodeGen-coreclrCLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants

@EgorBo@MichalPetryka@BrzVlad@AndyAyersMS
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Don't require GDV targets to be inlineable - #132375

Merged
EgorBo merged 5 commits into
dotnet:mainfrom
EgorBo:gdv-no-inline
Sep 1, 2026
Merged

Don't require GDV targets to be inlineable#132375
EgorBo merged 5 commits into
dotnet:mainfrom
EgorBo:gdv-no-inline

Conversation

@EgorBo

@EgorBoEgorBo commented Aug 16, 2026

Copy link
Copy Markdown
Member

GDV used to bail out when the target wasn't inlineable. A direct call still beats a virtual one, so keep the candidate and just don't inline it. DOTNET_JitGuardedDevirtualizationRequireInlining=1 restores the old behavior. Class probes only.

I know it doesn't match Andy's numbers, but many things have changed since then (e.g. CET), also, I assume we never tested it on arm64. So far it's a pure improvement on most kinds of scenarious (interface, virtual calls, etc.), see benchmarks below. We still might want to avoid doing it for virtual calls (esp for large target methods where the cost of the type check + direct call is a noise anyway) only on CoreCLR, but I propose we do it for everything and watch dotnet/performance reports.

Should help NativeAOT the most, where getExactClasses gives exact devirtualization but targets often aren't inlineable (if at least one target of getExactClasses wasn't inlineable we used to bail on the whole thing).

CoreCLR, default env, all targets [NoInlining] (so baseline does no GDV at all). Columns = how often the guessed type hits, ratio = new/base, lower is better.

BenchmarkMonoHit90Hit50Hit33Hit25
DirectControl (noise floor)1.010.991.011.011.00
Interface$\color{green}{0.72}$$\color{green}{0.48}$$\color{green}{0.45}$$\color{green}{0.64}$$\color{green}{0.56}$
InterfaceShared (__Canon)$\color{green}{0.88}$$\color{green}{0.50}$$\color{green}{0.51}$$\color{green}{0.62}$$\color{green}{0.59}$
InterfaceCostlyTarget$\color{green}{0.86}$$\color{green}{0.58}$$\color{green}{0.55}$$\color{green}{0.65}$$\color{green}{0.60}$
Virtual$\color{green}{0.86}$$\color{green}{0.78}$$\color{green}{0.63}$$\color{green}{0.64}$$\color{green}{0.62}$
VirtualShared (__Canon)$\color{red}{1.19}$$\color{green}{0.55}$$\color{green}{0.55}$$\color{green}{0.52}$$\color{green}{0.63}$
InterfaceHugeTarget (GDV in both)1.101.101.031.011.00

Only regression is monomorphic shared-generic virtual - a well-predicted vtable call is already as cheap as compare+direct call.

rt-sz report: MichalStrehovsky/rt-sz#246
smoke-test benchmarks: EgorBot/Benchmarks#489 (comment)

Guarded devirtualization used to give up whenever the target we'd devirtualize
to couldn't be inlined. But a direct call is still cheaper than a virtual or
interface call, so keep the candidate and just don't inline it.
Added JitGuardedDevirtualizationRequireInlining to get the old behavior back.
Class-based GDV only for now, method/delegate GDV is unchanged.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 38498b8d-c116-4637-b069-d1d275634193
CopilotAI lite review requested due to automatic review settings August 16, 2026 16:31
@github-actionsgithub-actionsBot added the area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI label Aug 16, 2026
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 5 pipeline(s).
11 pipeline(s) were filtered out due to trigger conditions.
There may be pipelines that require an authorized user to comment /azp run to run.

@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @JulieLeeMSFT, @jakobbotsch
See info in area-owners.md if you want to be subscribed.

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

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 adjusts CoreCLR JIT guarded devirtualization (GDV) so that class-based GDV candidates can be kept and expanded even when the devirtualized target isn’t inlineable, preserving the direct-call benefit. It also adds a config switch to restore the legacy “require inlineability” behavior and tightens up candidate bookkeeping to support the new flow.

Changes:

  • Add JitGuardedDevirtualizationRequireInlining config (default 0) to optionally restore legacy behavior of dropping non-inlineable GDV targets.
  • Track per-candidate inlineability via InlineCandidateInfo::isInlineable and keep non-inlineable class-GDV candidates for devirtualization-only.
  • Fix candidate bookkeeping (value-initialize InlineCandidateInfo and adjust spill-temp/enumerator-cloning mapping handling) to avoid relying on impCheckCanInline having run.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 1 comment.

Show a summary per file
FileDescription
src/coreclr/jit/jitmetadatalist.hAdds a new JIT metric counter (NoInlineGDV) for devirt-without-inlining cases.
src/coreclr/jit/jitconfigvalues.hIntroduces JitGuardedDevirtualizationRequireInlining config to gate legacy behavior.
src/coreclr/jit/inline.hAdds InlineCandidateInfo::isInlineable to distinguish inline vs devirt-only GDV candidates.
src/coreclr/jit/indirectcalltransformer.cppRemoves “must be inline-candidate” bail-out; expands GDV while conditionally re-marking inline candidates; fixes spill-temp and enumerator cloning map behavior.
src/coreclr/jit/importercalls.cppKeeps non-inlineable class-GDV candidates when legal; value-initializes candidate info; adjusts struct-return fixup deferral for GDV candidates.
src/coreclr/jit/compiler.hDeclares Compiler::canKeepNonInlineableGdvCandidate.

Comment threadsrc/coreclr/jit/importercalls.cpp Outdated
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 38498b8d-c116-4637-b069-d1d275634193
CopilotAI review requested due to automatic review settings August 16, 2026 16:53

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated no new comments.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 38498b8d-c116-4637-b069-d1d275634193
CopilotAI review requested due to automatic review settings August 16, 2026 17:09
@github-actionsgithub-actionsBot mentioned this pull request Aug 16, 2026

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated no new comments.

@MichalPetryka

Copy link
Copy Markdown
Contributor

@MihuBot -nuget

@EgorBo

Copy link
Copy Markdown
MemberAuthor

@MihuBot -nuget

@MichalPetryka This change requires PGO data, MihuBot is basically just PrepareMethod

@EgorBo

Copy link
Copy Markdown
MemberAuthor

/azp run runtime-coreclr pgostress, runtime-coreclr libraries-pgo

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 2 pipeline(s).

@EgorBo

Copy link
Copy Markdown
MemberAuthor

@EgorBot -linux_amd -osx_arm64 -windows_amd

usingSystem.Runtime.CompilerServices;usingBenchmarkDotNet.Attributes;usingBenchmarkDotNet.Running;BenchmarkSwitcher.FromAssembly(typeof(Bench).Assembly).Run(args);publicinterfaceIOp{intDo(intx);}publicclassOpA:IOp{[MethodImpl(MethodImplOptions.NoInlining)]publicintDo(intx)=>x+1;}publicclassOpB:IOp{[MethodImpl(MethodImplOptions.NoInlining)]publicintDo(intx)=>x+2;}publicabstractclassBase{publicabstractintDo(intx);}publicclassDerA:Base{[MethodImpl(MethodImplOptions.NoInlining)]publicoverrideintDo(intx)=>x+1;}publicclassDerB:Base{[MethodImpl(MethodImplOptions.NoInlining)]publicoverrideintDo(intx)=>x+2;}publicclassBench{privateIOp[]_ifaces=newIOp[64];privateBase[]_virts=newBase[64];[GlobalSetup]publicvoidSetup(){for(inti=0;i<64;i++){// 75% A, 25% B_ifaces[i]=i%4==0?newOpB():newOpA();_virts[i]=i%4==0?newDerB():(Base)newDerA();}}[Benchmark]publicintInterfaceCall(){intsum=0;IOp[]ops=_ifaces;for(inti=0;i<ops.Length;i++)sum+=ops[i].Do(i);returnsum;}[Benchmark]publicintVirtualCall(){intsum=0;Base[]objs=_virts;for(inti=0;i<objs.Length;i++)sum+=objs[i].Do(i);returnsum;}}

@EgorBo

Copy link
Copy Markdown
MemberAuthor

PTAL @AndyAyersMS (see PR description), .NET 12.0 change cc @dotnet/jit-contrib.

@EgorBo

EgorBo commented Aug 17, 2026

Copy link
Copy Markdown
MemberAuthor

cc @MichalStrehovsky I think you asked for it a while back for NAOT.

Comment threadsrc/coreclr/jit/importercalls.cpp Outdated
Comment threadsrc/coreclr/jit/importercalls.cpp
EgorBoand others added 2 commits August 26, 2026 18:10
… revoke dump
The tail-recursion-to-loop transform in fgMorphPotentialTailCall requires a
non-virtual callee, so bailing out of GDV for recursive implicit tail calls
didn't preserve the transform - it prevented it. Keep the candidate instead.
Also restore the 'Revoking guarded devirtualization candidate' JITDUMP.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings August 26, 2026 16:30

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated 2 comments.

Comment threadsrc/coreclr/jit/inline.h
Comment threadsrc/coreclr/jit/jitconfigvalues.h
@EgorBo

Copy link
Copy Markdown
MemberAuthor

@AndyAyersMS addressed your feedback, it now needs re-approval 🙂

@EgorBo

Copy link
Copy Markdown
MemberAuthor

Ping @AndyAyersMS

@EgorBo
EgorBo enabled auto-merge (squash) September 1, 2026 11:20
@EgorBo
EgorBo merged commit 0055ffd into dotnet:mainSep 1, 2026
134 checks passed
@BrzVlad

Copy link
Copy Markdown
Member

@EgorBo I suspect this started causing crashes on my PR (#132787). Thoughts ?

 /__w/1/s/artifacts/bin/coreclr/linux.x64.Checked/x64/crossgen2/crossgen2 -o:/__w/1/s/artifacts/bin/coreclr/linux.x64.Checked/System.Private.CoreLib.dll -r:/__w/1/s/artifacts/bin/coreclr/linux.x64.Checked/IL/*.dll --targetarch:x64 --obj-format:pe --targetos:linux --target-allows-runtime-code-generation:true -m:/__w/1/s/artifacts/bin/coreclr/linux.x64.Checked/StandardOptimizationData.mibc --embed-pgo-data -O --verify-type-and-field-layout --enable-cached-interface-dispatch-support /__w/1/s/artifacts/bin/coreclr/linux.x64.Checked/IL/System.Private.CoreLib.dll --perfmap-format-version:1 --perfmap --perfmap-path:/__w/1/s/artifacts/bin/coreclr/linux.x64.Checked/
/__w/1/s/src/coreclr/jit/fgdiagnostic.cpp:3416
Assertion failed 'block->HasFlag(BBF_RECURSIVE_TAILCALL)' in 'System.Type:GetTypeCodeImpl():int:this' during 'Morph - Inlining' (IL size 23; hash 0xf87e7a03; FullOpts)

@EgorBo

EgorBo commented Sep 1, 2026

Copy link
Copy Markdown
MemberAuthor

@EgorBo I suspect this started causing crashes on my PR (#132787). Thoughts ?

 /__w/1/s/artifacts/bin/coreclr/linux.x64.Checked/x64/crossgen2/crossgen2 -o:/__w/1/s/artifacts/bin/coreclr/linux.x64.Checked/System.Private.CoreLib.dll -r:/__w/1/s/artifacts/bin/coreclr/linux.x64.Checked/IL/*.dll --targetarch:x64 --obj-format:pe --targetos:linux --target-allows-runtime-code-generation:true -m:/__w/1/s/artifacts/bin/coreclr/linux.x64.Checked/StandardOptimizationData.mibc --embed-pgo-data -O --verify-type-and-field-layout --enable-cached-interface-dispatch-support /__w/1/s/artifacts/bin/coreclr/linux.x64.Checked/IL/System.Private.CoreLib.dll --perfmap-format-version:1 --perfmap --perfmap-path:/__w/1/s/artifacts/bin/coreclr/linux.x64.Checked/
/__w/1/s/src/coreclr/jit/fgdiagnostic.cpp:3416
Assertion failed 'block->HasFlag(BBF_RECURSIVE_TAILCALL)' in 'System.Type:GetTypeCodeImpl():int:this' during 'Morph - Inlining' (IL size 23; hash 0xf87e7a03; FullOpts)

Yep, sorry, it's a bad last-minute feedback address change, I'll file a fix in a minute

@EgorBo

EgorBo commented Sep 1, 2026

Copy link
Copy Markdown
MemberAuthor

#133014

Fixed.

EgorBo added a commit that referenced this pull request Sep 1, 2026
Main is currently broken in Checked CoreCLR builds while crossgenning
`System.Private.CoreLib`:
```
Assertion failed 'block->HasFlag(BBF_RECURSIVE_TAILCALL)' in 'System.Type:GetTypeCodeImpl():int:this' during 'Morph - Inlining'
```
#132375 began retaining non-inlineable GDV candidates for implicit
recursive tail calls after [this review
discussion](#132375 (comment)).
GDV expansion then moves the residual recursive call into a new block
without its recursive-tailcall block marker, triggering the assertion.
Conservatively avoid retaining non-inlineable GDV candidates for all
tail calls. Inlineable implicit-tail-call GDV remains unchanged. We can
be less conservative, but that requires changes in the GDV expansion
code (at very least - mark blocks as containing a tail call, ensure its
shape is correct).
Validation:
- `build.cmd Clr.Jit -c Checked`
- Re-ran the failing Crossgen2 command
- SuperPMI replay of `benchmarks.run.` (56,322 contexts)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 0458833e-ff11-4cb8-9ca0-8e06f2034c2d
@EgorBo
EgorBo deleted the gdv-no-inline branch September 1, 2026 14:01
@dotnet-milestone-botdotnet-milestone-botBot added this to the 12.0-preview1 milestone Sep 2, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area-CodeGen-coreclrCLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants

@EgorBo@MichalPetryka@BrzVlad@AndyAyersMS
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Don't require GDV targets to be inlineable - #132375

Merged
EgorBo merged 5 commits into
dotnet:mainfrom
EgorBo:gdv-no-inline
Sep 1, 2026
Merged

Don't require GDV targets to be inlineable#132375
EgorBo merged 5 commits into
dotnet:mainfrom
EgorBo:gdv-no-inline

Conversation

@EgorBo

@EgorBoEgorBo commented Aug 16, 2026

Copy link
Copy Markdown
Member

GDV used to bail out when the target wasn't inlineable. A direct call still beats a virtual one, so keep the candidate and just don't inline it. DOTNET_JitGuardedDevirtualizationRequireInlining=1 restores the old behavior. Class probes only.

I know it doesn't match Andy's numbers, but many things have changed since then (e.g. CET), also, I assume we never tested it on arm64. So far it's a pure improvement on most kinds of scenarious (interface, virtual calls, etc.), see benchmarks below. We still might want to avoid doing it for virtual calls (esp for large target methods where the cost of the type check + direct call is a noise anyway) only on CoreCLR, but I propose we do it for everything and watch dotnet/performance reports.

Should help NativeAOT the most, where getExactClasses gives exact devirtualization but targets often aren't inlineable (if at least one target of getExactClasses wasn't inlineable we used to bail on the whole thing).

CoreCLR, default env, all targets [NoInlining] (so baseline does no GDV at all). Columns = how often the guessed type hits, ratio = new/base, lower is better.

BenchmarkMonoHit90Hit50Hit33Hit25
DirectControl (noise floor)1.010.991.011.011.00
Interface$\color{green}{0.72}$$\color{green}{0.48}$$\color{green}{0.45}$$\color{green}{0.64}$$\color{green}{0.56}$
InterfaceShared (__Canon)$\color{green}{0.88}$$\color{green}{0.50}$$\color{green}{0.51}$$\color{green}{0.62}$$\color{green}{0.59}$
InterfaceCostlyTarget$\color{green}{0.86}$$\color{green}{0.58}$$\color{green}{0.55}$$\color{green}{0.65}$$\color{green}{0.60}$
Virtual$\color{green}{0.86}$$\color{green}{0.78}$$\color{green}{0.63}$$\color{green}{0.64}$$\color{green}{0.62}$
VirtualShared (__Canon)$\color{red}{1.19}$$\color{green}{0.55}$$\color{green}{0.55}$$\color{green}{0.52}$$\color{green}{0.63}$
InterfaceHugeTarget (GDV in both)1.101.101.031.011.00

Only regression is monomorphic shared-generic virtual - a well-predicted vtable call is already as cheap as compare+direct call.

rt-sz report: MichalStrehovsky/rt-sz#246
smoke-test benchmarks: EgorBot/Benchmarks#489 (comment)

Guarded devirtualization used to give up whenever the target we'd devirtualize
to couldn't be inlined. But a direct call is still cheaper than a virtual or
interface call, so keep the candidate and just don't inline it.
Added JitGuardedDevirtualizationRequireInlining to get the old behavior back.
Class-based GDV only for now, method/delegate GDV is unchanged.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 38498b8d-c116-4637-b069-d1d275634193
CopilotAI lite review requested due to automatic review settings August 16, 2026 16:31
@github-actionsgithub-actionsBot added the area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI label Aug 16, 2026
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 5 pipeline(s).
11 pipeline(s) were filtered out due to trigger conditions.
There may be pipelines that require an authorized user to comment /azp run to run.

@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @JulieLeeMSFT, @jakobbotsch
See info in area-owners.md if you want to be subscribed.

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

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 adjusts CoreCLR JIT guarded devirtualization (GDV) so that class-based GDV candidates can be kept and expanded even when the devirtualized target isn’t inlineable, preserving the direct-call benefit. It also adds a config switch to restore the legacy “require inlineability” behavior and tightens up candidate bookkeeping to support the new flow.

Changes:

  • Add JitGuardedDevirtualizationRequireInlining config (default 0) to optionally restore legacy behavior of dropping non-inlineable GDV targets.
  • Track per-candidate inlineability via InlineCandidateInfo::isInlineable and keep non-inlineable class-GDV candidates for devirtualization-only.
  • Fix candidate bookkeeping (value-initialize InlineCandidateInfo and adjust spill-temp/enumerator-cloning mapping handling) to avoid relying on impCheckCanInline having run.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 1 comment.

Show a summary per file
FileDescription
src/coreclr/jit/jitmetadatalist.hAdds a new JIT metric counter (NoInlineGDV) for devirt-without-inlining cases.
src/coreclr/jit/jitconfigvalues.hIntroduces JitGuardedDevirtualizationRequireInlining config to gate legacy behavior.
src/coreclr/jit/inline.hAdds InlineCandidateInfo::isInlineable to distinguish inline vs devirt-only GDV candidates.
src/coreclr/jit/indirectcalltransformer.cppRemoves “must be inline-candidate” bail-out; expands GDV while conditionally re-marking inline candidates; fixes spill-temp and enumerator cloning map behavior.
src/coreclr/jit/importercalls.cppKeeps non-inlineable class-GDV candidates when legal; value-initializes candidate info; adjusts struct-return fixup deferral for GDV candidates.
src/coreclr/jit/compiler.hDeclares Compiler::canKeepNonInlineableGdvCandidate.

Comment threadsrc/coreclr/jit/importercalls.cpp Outdated
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 38498b8d-c116-4637-b069-d1d275634193
CopilotAI review requested due to automatic review settings August 16, 2026 16:53

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated no new comments.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 38498b8d-c116-4637-b069-d1d275634193
CopilotAI review requested due to automatic review settings August 16, 2026 17:09
@github-actionsgithub-actionsBot mentioned this pull request Aug 16, 2026

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated no new comments.

@MichalPetryka

Copy link
Copy Markdown
Contributor

@MihuBot -nuget

@EgorBo

Copy link
Copy Markdown
MemberAuthor

@MihuBot -nuget

@MichalPetryka This change requires PGO data, MihuBot is basically just PrepareMethod

@EgorBo

Copy link
Copy Markdown
MemberAuthor

/azp run runtime-coreclr pgostress, runtime-coreclr libraries-pgo

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 2 pipeline(s).

@EgorBo

Copy link
Copy Markdown
MemberAuthor

@EgorBot -linux_amd -osx_arm64 -windows_amd

usingSystem.Runtime.CompilerServices;usingBenchmarkDotNet.Attributes;usingBenchmarkDotNet.Running;BenchmarkSwitcher.FromAssembly(typeof(Bench).Assembly).Run(args);publicinterfaceIOp{intDo(intx);}publicclassOpA:IOp{[MethodImpl(MethodImplOptions.NoInlining)]publicintDo(intx)=>x+1;}publicclassOpB:IOp{[MethodImpl(MethodImplOptions.NoInlining)]publicintDo(intx)=>x+2;}publicabstractclassBase{publicabstractintDo(intx);}publicclassDerA:Base{[MethodImpl(MethodImplOptions.NoInlining)]publicoverrideintDo(intx)=>x+1;}publicclassDerB:Base{[MethodImpl(MethodImplOptions.NoInlining)]publicoverrideintDo(intx)=>x+2;}publicclassBench{privateIOp[]_ifaces=newIOp[64];privateBase[]_virts=newBase[64];[GlobalSetup]publicvoidSetup(){for(inti=0;i<64;i++){// 75% A, 25% B_ifaces[i]=i%4==0?newOpB():newOpA();_virts[i]=i%4==0?newDerB():(Base)newDerA();}}[Benchmark]publicintInterfaceCall(){intsum=0;IOp[]ops=_ifaces;for(inti=0;i<ops.Length;i++)sum+=ops[i].Do(i);returnsum;}[Benchmark]publicintVirtualCall(){intsum=0;Base[]objs=_virts;for(inti=0;i<objs.Length;i++)sum+=objs[i].Do(i);returnsum;}}

@EgorBo

Copy link
Copy Markdown
MemberAuthor

PTAL @AndyAyersMS (see PR description), .NET 12.0 change cc @dotnet/jit-contrib.

@EgorBo

EgorBo commented Aug 17, 2026

Copy link
Copy Markdown
MemberAuthor

cc @MichalStrehovsky I think you asked for it a while back for NAOT.

Comment threadsrc/coreclr/jit/importercalls.cpp Outdated
Comment threadsrc/coreclr/jit/importercalls.cpp
EgorBoand others added 2 commits August 26, 2026 18:10
… revoke dump
The tail-recursion-to-loop transform in fgMorphPotentialTailCall requires a
non-virtual callee, so bailing out of GDV for recursive implicit tail calls
didn't preserve the transform - it prevented it. Keep the candidate instead.
Also restore the 'Revoking guarded devirtualization candidate' JITDUMP.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings August 26, 2026 16:30

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated 2 comments.

Comment threadsrc/coreclr/jit/inline.h
Comment threadsrc/coreclr/jit/jitconfigvalues.h
@EgorBo

Copy link
Copy Markdown
MemberAuthor

@AndyAyersMS addressed your feedback, it now needs re-approval 🙂

@EgorBo

Copy link
Copy Markdown
MemberAuthor

Ping @AndyAyersMS

@EgorBo
EgorBo enabled auto-merge (squash) September 1, 2026 11:20
@EgorBo
EgorBo merged commit 0055ffd into dotnet:mainSep 1, 2026
134 checks passed
@BrzVlad

Copy link
Copy Markdown
Member

@EgorBo I suspect this started causing crashes on my PR (#132787). Thoughts ?

 /__w/1/s/artifacts/bin/coreclr/linux.x64.Checked/x64/crossgen2/crossgen2 -o:/__w/1/s/artifacts/bin/coreclr/linux.x64.Checked/System.Private.CoreLib.dll -r:/__w/1/s/artifacts/bin/coreclr/linux.x64.Checked/IL/*.dll --targetarch:x64 --obj-format:pe --targetos:linux --target-allows-runtime-code-generation:true -m:/__w/1/s/artifacts/bin/coreclr/linux.x64.Checked/StandardOptimizationData.mibc --embed-pgo-data -O --verify-type-and-field-layout --enable-cached-interface-dispatch-support /__w/1/s/artifacts/bin/coreclr/linux.x64.Checked/IL/System.Private.CoreLib.dll --perfmap-format-version:1 --perfmap --perfmap-path:/__w/1/s/artifacts/bin/coreclr/linux.x64.Checked/
/__w/1/s/src/coreclr/jit/fgdiagnostic.cpp:3416
Assertion failed 'block->HasFlag(BBF_RECURSIVE_TAILCALL)' in 'System.Type:GetTypeCodeImpl():int:this' during 'Morph - Inlining' (IL size 23; hash 0xf87e7a03; FullOpts)

@EgorBo

EgorBo commented Sep 1, 2026

Copy link
Copy Markdown
MemberAuthor

@EgorBo I suspect this started causing crashes on my PR (#132787). Thoughts ?

 /__w/1/s/artifacts/bin/coreclr/linux.x64.Checked/x64/crossgen2/crossgen2 -o:/__w/1/s/artifacts/bin/coreclr/linux.x64.Checked/System.Private.CoreLib.dll -r:/__w/1/s/artifacts/bin/coreclr/linux.x64.Checked/IL/*.dll --targetarch:x64 --obj-format:pe --targetos:linux --target-allows-runtime-code-generation:true -m:/__w/1/s/artifacts/bin/coreclr/linux.x64.Checked/StandardOptimizationData.mibc --embed-pgo-data -O --verify-type-and-field-layout --enable-cached-interface-dispatch-support /__w/1/s/artifacts/bin/coreclr/linux.x64.Checked/IL/System.Private.CoreLib.dll --perfmap-format-version:1 --perfmap --perfmap-path:/__w/1/s/artifacts/bin/coreclr/linux.x64.Checked/
/__w/1/s/src/coreclr/jit/fgdiagnostic.cpp:3416
Assertion failed 'block->HasFlag(BBF_RECURSIVE_TAILCALL)' in 'System.Type:GetTypeCodeImpl():int:this' during 'Morph - Inlining' (IL size 23; hash 0xf87e7a03; FullOpts)

Yep, sorry, it's a bad last-minute feedback address change, I'll file a fix in a minute

@EgorBo

EgorBo commented Sep 1, 2026

Copy link
Copy Markdown
MemberAuthor

#133014

Fixed.

EgorBo added a commit that referenced this pull request Sep 1, 2026
Main is currently broken in Checked CoreCLR builds while crossgenning
`System.Private.CoreLib`:
```
Assertion failed 'block->HasFlag(BBF_RECURSIVE_TAILCALL)' in 'System.Type:GetTypeCodeImpl():int:this' during 'Morph - Inlining'
```
#132375 began retaining non-inlineable GDV candidates for implicit
recursive tail calls after [this review
discussion](#132375 (comment)).
GDV expansion then moves the residual recursive call into a new block
without its recursive-tailcall block marker, triggering the assertion.
Conservatively avoid retaining non-inlineable GDV candidates for all
tail calls. Inlineable implicit-tail-call GDV remains unchanged. We can
be less conservative, but that requires changes in the GDV expansion
code (at very least - mark blocks as containing a tail call, ensure its
shape is correct).
Validation:
- `build.cmd Clr.Jit -c Checked`
- Re-ran the failing Crossgen2 command
- SuperPMI replay of `benchmarks.run.` (56,322 contexts)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 0458833e-ff11-4cb8-9ca0-8e06f2034c2d
@EgorBo
EgorBo deleted the gdv-no-inline branch September 1, 2026 14:01
@dotnet-milestone-botdotnet-milestone-botBot added this to the 12.0-preview1 milestone Sep 2, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area-CodeGen-coreclrCLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants

@EgorBo@MichalPetryka@BrzVlad@AndyAyersMS
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Don't require GDV targets to be inlineable - #132375

Merged
EgorBo merged 5 commits into
dotnet:mainfrom
EgorBo:gdv-no-inline
Sep 1, 2026
Merged

Don't require GDV targets to be inlineable#132375
EgorBo merged 5 commits into
dotnet:mainfrom
EgorBo:gdv-no-inline

Conversation

@EgorBo

@EgorBoEgorBo commented Aug 16, 2026

Copy link
Copy Markdown
Member

GDV used to bail out when the target wasn't inlineable. A direct call still beats a virtual one, so keep the candidate and just don't inline it. DOTNET_JitGuardedDevirtualizationRequireInlining=1 restores the old behavior. Class probes only.

I know it doesn't match Andy's numbers, but many things have changed since then (e.g. CET), also, I assume we never tested it on arm64. So far it's a pure improvement on most kinds of scenarious (interface, virtual calls, etc.), see benchmarks below. We still might want to avoid doing it for virtual calls (esp for large target methods where the cost of the type check + direct call is a noise anyway) only on CoreCLR, but I propose we do it for everything and watch dotnet/performance reports.

Should help NativeAOT the most, where getExactClasses gives exact devirtualization but targets often aren't inlineable (if at least one target of getExactClasses wasn't inlineable we used to bail on the whole thing).

CoreCLR, default env, all targets [NoInlining] (so baseline does no GDV at all). Columns = how often the guessed type hits, ratio = new/base, lower is better.

BenchmarkMonoHit90Hit50Hit33Hit25
DirectControl (noise floor)1.010.991.011.011.00
Interface$\color{green}{0.72}$$\color{green}{0.48}$$\color{green}{0.45}$$\color{green}{0.64}$$\color{green}{0.56}$
InterfaceShared (__Canon)$\color{green}{0.88}$$\color{green}{0.50}$$\color{green}{0.51}$$\color{green}{0.62}$$\color{green}{0.59}$
InterfaceCostlyTarget$\color{green}{0.86}$$\color{green}{0.58}$$\color{green}{0.55}$$\color{green}{0.65}$$\color{green}{0.60}$
Virtual$\color{green}{0.86}$$\color{green}{0.78}$$\color{green}{0.63}$$\color{green}{0.64}$$\color{green}{0.62}$
VirtualShared (__Canon)$\color{red}{1.19}$$\color{green}{0.55}$$\color{green}{0.55}$$\color{green}{0.52}$$\color{green}{0.63}$
InterfaceHugeTarget (GDV in both)1.101.101.031.011.00

Only regression is monomorphic shared-generic virtual - a well-predicted vtable call is already as cheap as compare+direct call.

rt-sz report: MichalStrehovsky/rt-sz#246
smoke-test benchmarks: EgorBot/Benchmarks#489 (comment)

Guarded devirtualization used to give up whenever the target we'd devirtualize
to couldn't be inlined. But a direct call is still cheaper than a virtual or
interface call, so keep the candidate and just don't inline it.
Added JitGuardedDevirtualizationRequireInlining to get the old behavior back.
Class-based GDV only for now, method/delegate GDV is unchanged.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 38498b8d-c116-4637-b069-d1d275634193
CopilotAI lite review requested due to automatic review settings August 16, 2026 16:31
@github-actionsgithub-actionsBot added the area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI label Aug 16, 2026
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 5 pipeline(s).
11 pipeline(s) were filtered out due to trigger conditions.
There may be pipelines that require an authorized user to comment /azp run to run.

@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @JulieLeeMSFT, @jakobbotsch
See info in area-owners.md if you want to be subscribed.

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

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 adjusts CoreCLR JIT guarded devirtualization (GDV) so that class-based GDV candidates can be kept and expanded even when the devirtualized target isn’t inlineable, preserving the direct-call benefit. It also adds a config switch to restore the legacy “require inlineability” behavior and tightens up candidate bookkeeping to support the new flow.

Changes:

  • Add JitGuardedDevirtualizationRequireInlining config (default 0) to optionally restore legacy behavior of dropping non-inlineable GDV targets.
  • Track per-candidate inlineability via InlineCandidateInfo::isInlineable and keep non-inlineable class-GDV candidates for devirtualization-only.
  • Fix candidate bookkeeping (value-initialize InlineCandidateInfo and adjust spill-temp/enumerator-cloning mapping handling) to avoid relying on impCheckCanInline having run.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 1 comment.

Show a summary per file
FileDescription
src/coreclr/jit/jitmetadatalist.hAdds a new JIT metric counter (NoInlineGDV) for devirt-without-inlining cases.
src/coreclr/jit/jitconfigvalues.hIntroduces JitGuardedDevirtualizationRequireInlining config to gate legacy behavior.
src/coreclr/jit/inline.hAdds InlineCandidateInfo::isInlineable to distinguish inline vs devirt-only GDV candidates.
src/coreclr/jit/indirectcalltransformer.cppRemoves “must be inline-candidate” bail-out; expands GDV while conditionally re-marking inline candidates; fixes spill-temp and enumerator cloning map behavior.
src/coreclr/jit/importercalls.cppKeeps non-inlineable class-GDV candidates when legal; value-initializes candidate info; adjusts struct-return fixup deferral for GDV candidates.
src/coreclr/jit/compiler.hDeclares Compiler::canKeepNonInlineableGdvCandidate.

Comment threadsrc/coreclr/jit/importercalls.cpp Outdated
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 38498b8d-c116-4637-b069-d1d275634193
CopilotAI review requested due to automatic review settings August 16, 2026 16:53

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated no new comments.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 38498b8d-c116-4637-b069-d1d275634193
CopilotAI review requested due to automatic review settings August 16, 2026 17:09
@github-actionsgithub-actionsBot mentioned this pull request Aug 16, 2026

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated no new comments.

@MichalPetryka

Copy link
Copy Markdown
Contributor

@MihuBot -nuget

@EgorBo

Copy link
Copy Markdown
MemberAuthor

@MihuBot -nuget

@MichalPetryka This change requires PGO data, MihuBot is basically just PrepareMethod

@EgorBo

Copy link
Copy Markdown
MemberAuthor

/azp run runtime-coreclr pgostress, runtime-coreclr libraries-pgo

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 2 pipeline(s).

@EgorBo

Copy link
Copy Markdown
MemberAuthor

@EgorBot -linux_amd -osx_arm64 -windows_amd

usingSystem.Runtime.CompilerServices;usingBenchmarkDotNet.Attributes;usingBenchmarkDotNet.Running;BenchmarkSwitcher.FromAssembly(typeof(Bench).Assembly).Run(args);publicinterfaceIOp{intDo(intx);}publicclassOpA:IOp{[MethodImpl(MethodImplOptions.NoInlining)]publicintDo(intx)=>x+1;}publicclassOpB:IOp{[MethodImpl(MethodImplOptions.NoInlining)]publicintDo(intx)=>x+2;}publicabstractclassBase{publicabstractintDo(intx);}publicclassDerA:Base{[MethodImpl(MethodImplOptions.NoInlining)]publicoverrideintDo(intx)=>x+1;}publicclassDerB:Base{[MethodImpl(MethodImplOptions.NoInlining)]publicoverrideintDo(intx)=>x+2;}publicclassBench{privateIOp[]_ifaces=newIOp[64];privateBase[]_virts=newBase[64];[GlobalSetup]publicvoidSetup(){for(inti=0;i<64;i++){// 75% A, 25% B_ifaces[i]=i%4==0?newOpB():newOpA();_virts[i]=i%4==0?newDerB():(Base)newDerA();}}[Benchmark]publicintInterfaceCall(){intsum=0;IOp[]ops=_ifaces;for(inti=0;i<ops.Length;i++)sum+=ops[i].Do(i);returnsum;}[Benchmark]publicintVirtualCall(){intsum=0;Base[]objs=_virts;for(inti=0;i<objs.Length;i++)sum+=objs[i].Do(i);returnsum;}}

@EgorBo

Copy link
Copy Markdown
MemberAuthor

PTAL @AndyAyersMS (see PR description), .NET 12.0 change cc @dotnet/jit-contrib.

@EgorBo

EgorBo commented Aug 17, 2026

Copy link
Copy Markdown
MemberAuthor

cc @MichalStrehovsky I think you asked for it a while back for NAOT.

Comment threadsrc/coreclr/jit/importercalls.cpp Outdated
Comment threadsrc/coreclr/jit/importercalls.cpp
EgorBoand others added 2 commits August 26, 2026 18:10
… revoke dump
The tail-recursion-to-loop transform in fgMorphPotentialTailCall requires a
non-virtual callee, so bailing out of GDV for recursive implicit tail calls
didn't preserve the transform - it prevented it. Keep the candidate instead.
Also restore the 'Revoking guarded devirtualization candidate' JITDUMP.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings August 26, 2026 16:30

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated 2 comments.

Comment threadsrc/coreclr/jit/inline.h
Comment threadsrc/coreclr/jit/jitconfigvalues.h
@EgorBo

Copy link
Copy Markdown
MemberAuthor

@AndyAyersMS addressed your feedback, it now needs re-approval 🙂

@EgorBo

Copy link
Copy Markdown
MemberAuthor

Ping @AndyAyersMS

@EgorBo
EgorBo enabled auto-merge (squash) September 1, 2026 11:20
@EgorBo
EgorBo merged commit 0055ffd into dotnet:mainSep 1, 2026
134 checks passed
@BrzVlad

Copy link
Copy Markdown
Member

@EgorBo I suspect this started causing crashes on my PR (#132787). Thoughts ?

 /__w/1/s/artifacts/bin/coreclr/linux.x64.Checked/x64/crossgen2/crossgen2 -o:/__w/1/s/artifacts/bin/coreclr/linux.x64.Checked/System.Private.CoreLib.dll -r:/__w/1/s/artifacts/bin/coreclr/linux.x64.Checked/IL/*.dll --targetarch:x64 --obj-format:pe --targetos:linux --target-allows-runtime-code-generation:true -m:/__w/1/s/artifacts/bin/coreclr/linux.x64.Checked/StandardOptimizationData.mibc --embed-pgo-data -O --verify-type-and-field-layout --enable-cached-interface-dispatch-support /__w/1/s/artifacts/bin/coreclr/linux.x64.Checked/IL/System.Private.CoreLib.dll --perfmap-format-version:1 --perfmap --perfmap-path:/__w/1/s/artifacts/bin/coreclr/linux.x64.Checked/
/__w/1/s/src/coreclr/jit/fgdiagnostic.cpp:3416
Assertion failed 'block->HasFlag(BBF_RECURSIVE_TAILCALL)' in 'System.Type:GetTypeCodeImpl():int:this' during 'Morph - Inlining' (IL size 23; hash 0xf87e7a03; FullOpts)

@EgorBo

EgorBo commented Sep 1, 2026

Copy link
Copy Markdown
MemberAuthor

@EgorBo I suspect this started causing crashes on my PR (#132787). Thoughts ?

 /__w/1/s/artifacts/bin/coreclr/linux.x64.Checked/x64/crossgen2/crossgen2 -o:/__w/1/s/artifacts/bin/coreclr/linux.x64.Checked/System.Private.CoreLib.dll -r:/__w/1/s/artifacts/bin/coreclr/linux.x64.Checked/IL/*.dll --targetarch:x64 --obj-format:pe --targetos:linux --target-allows-runtime-code-generation:true -m:/__w/1/s/artifacts/bin/coreclr/linux.x64.Checked/StandardOptimizationData.mibc --embed-pgo-data -O --verify-type-and-field-layout --enable-cached-interface-dispatch-support /__w/1/s/artifacts/bin/coreclr/linux.x64.Checked/IL/System.Private.CoreLib.dll --perfmap-format-version:1 --perfmap --perfmap-path:/__w/1/s/artifacts/bin/coreclr/linux.x64.Checked/
/__w/1/s/src/coreclr/jit/fgdiagnostic.cpp:3416
Assertion failed 'block->HasFlag(BBF_RECURSIVE_TAILCALL)' in 'System.Type:GetTypeCodeImpl():int:this' during 'Morph - Inlining' (IL size 23; hash 0xf87e7a03; FullOpts)

Yep, sorry, it's a bad last-minute feedback address change, I'll file a fix in a minute

@EgorBo

EgorBo commented Sep 1, 2026

Copy link
Copy Markdown
MemberAuthor

#133014

Fixed.

EgorBo added a commit that referenced this pull request Sep 1, 2026
Main is currently broken in Checked CoreCLR builds while crossgenning
`System.Private.CoreLib`:
```
Assertion failed 'block->HasFlag(BBF_RECURSIVE_TAILCALL)' in 'System.Type:GetTypeCodeImpl():int:this' during 'Morph - Inlining'
```
#132375 began retaining non-inlineable GDV candidates for implicit
recursive tail calls after [this review
discussion](#132375 (comment)).
GDV expansion then moves the residual recursive call into a new block
without its recursive-tailcall block marker, triggering the assertion.
Conservatively avoid retaining non-inlineable GDV candidates for all
tail calls. Inlineable implicit-tail-call GDV remains unchanged. We can
be less conservative, but that requires changes in the GDV expansion
code (at very least - mark blocks as containing a tail call, ensure its
shape is correct).
Validation:
- `build.cmd Clr.Jit -c Checked`
- Re-ran the failing Crossgen2 command
- SuperPMI replay of `benchmarks.run.` (56,322 contexts)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 0458833e-ff11-4cb8-9ca0-8e06f2034c2d
@EgorBo
EgorBo deleted the gdv-no-inline branch September 1, 2026 14:01
@dotnet-milestone-botdotnet-milestone-botBot added this to the 12.0-preview1 milestone Sep 2, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area-CodeGen-coreclrCLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants

@EgorBo@MichalPetryka@BrzVlad@AndyAyersMS
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Don't require GDV targets to be inlineable - #132375

Merged
EgorBo merged 5 commits into
dotnet:mainfrom
EgorBo:gdv-no-inline
Sep 1, 2026
Merged

Don't require GDV targets to be inlineable#132375
EgorBo merged 5 commits into
dotnet:mainfrom
EgorBo:gdv-no-inline

Conversation

@EgorBo

@EgorBoEgorBo commented Aug 16, 2026

Copy link
Copy Markdown
Member

GDV used to bail out when the target wasn't inlineable. A direct call still beats a virtual one, so keep the candidate and just don't inline it. DOTNET_JitGuardedDevirtualizationRequireInlining=1 restores the old behavior. Class probes only.

I know it doesn't match Andy's numbers, but many things have changed since then (e.g. CET), also, I assume we never tested it on arm64. So far it's a pure improvement on most kinds of scenarious (interface, virtual calls, etc.), see benchmarks below. We still might want to avoid doing it for virtual calls (esp for large target methods where the cost of the type check + direct call is a noise anyway) only on CoreCLR, but I propose we do it for everything and watch dotnet/performance reports.

Should help NativeAOT the most, where getExactClasses gives exact devirtualization but targets often aren't inlineable (if at least one target of getExactClasses wasn't inlineable we used to bail on the whole thing).

CoreCLR, default env, all targets [NoInlining] (so baseline does no GDV at all). Columns = how often the guessed type hits, ratio = new/base, lower is better.

BenchmarkMonoHit90Hit50Hit33Hit25
DirectControl (noise floor)1.010.991.011.011.00
Interface$\color{green}{0.72}$$\color{green}{0.48}$$\color{green}{0.45}$$\color{green}{0.64}$$\color{green}{0.56}$
InterfaceShared (__Canon)$\color{green}{0.88}$$\color{green}{0.50}$$\color{green}{0.51}$$\color{green}{0.62}$$\color{green}{0.59}$
InterfaceCostlyTarget$\color{green}{0.86}$$\color{green}{0.58}$$\color{green}{0.55}$$\color{green}{0.65}$$\color{green}{0.60}$
Virtual$\color{green}{0.86}$$\color{green}{0.78}$$\color{green}{0.63}$$\color{green}{0.64}$$\color{green}{0.62}$
VirtualShared (__Canon)$\color{red}{1.19}$$\color{green}{0.55}$$\color{green}{0.55}$$\color{green}{0.52}$$\color{green}{0.63}$
InterfaceHugeTarget (GDV in both)1.101.101.031.011.00

Only regression is monomorphic shared-generic virtual - a well-predicted vtable call is already as cheap as compare+direct call.

rt-sz report: MichalStrehovsky/rt-sz#246
smoke-test benchmarks: EgorBot/Benchmarks#489 (comment)

Guarded devirtualization used to give up whenever the target we'd devirtualize
to couldn't be inlined. But a direct call is still cheaper than a virtual or
interface call, so keep the candidate and just don't inline it.
Added JitGuardedDevirtualizationRequireInlining to get the old behavior back.
Class-based GDV only for now, method/delegate GDV is unchanged.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 38498b8d-c116-4637-b069-d1d275634193
CopilotAI lite review requested due to automatic review settings August 16, 2026 16:31
@github-actionsgithub-actionsBot added the area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI label Aug 16, 2026
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 5 pipeline(s).
11 pipeline(s) were filtered out due to trigger conditions.
There may be pipelines that require an authorized user to comment /azp run to run.

@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @JulieLeeMSFT, @jakobbotsch
See info in area-owners.md if you want to be subscribed.

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

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 adjusts CoreCLR JIT guarded devirtualization (GDV) so that class-based GDV candidates can be kept and expanded even when the devirtualized target isn’t inlineable, preserving the direct-call benefit. It also adds a config switch to restore the legacy “require inlineability” behavior and tightens up candidate bookkeeping to support the new flow.

Changes:

  • Add JitGuardedDevirtualizationRequireInlining config (default 0) to optionally restore legacy behavior of dropping non-inlineable GDV targets.
  • Track per-candidate inlineability via InlineCandidateInfo::isInlineable and keep non-inlineable class-GDV candidates for devirtualization-only.
  • Fix candidate bookkeeping (value-initialize InlineCandidateInfo and adjust spill-temp/enumerator-cloning mapping handling) to avoid relying on impCheckCanInline having run.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 1 comment.

Show a summary per file
FileDescription
src/coreclr/jit/jitmetadatalist.hAdds a new JIT metric counter (NoInlineGDV) for devirt-without-inlining cases.
src/coreclr/jit/jitconfigvalues.hIntroduces JitGuardedDevirtualizationRequireInlining config to gate legacy behavior.
src/coreclr/jit/inline.hAdds InlineCandidateInfo::isInlineable to distinguish inline vs devirt-only GDV candidates.
src/coreclr/jit/indirectcalltransformer.cppRemoves “must be inline-candidate” bail-out; expands GDV while conditionally re-marking inline candidates; fixes spill-temp and enumerator cloning map behavior.
src/coreclr/jit/importercalls.cppKeeps non-inlineable class-GDV candidates when legal; value-initializes candidate info; adjusts struct-return fixup deferral for GDV candidates.
src/coreclr/jit/compiler.hDeclares Compiler::canKeepNonInlineableGdvCandidate.

Comment threadsrc/coreclr/jit/importercalls.cpp Outdated
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 38498b8d-c116-4637-b069-d1d275634193
CopilotAI review requested due to automatic review settings August 16, 2026 16:53

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated no new comments.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 38498b8d-c116-4637-b069-d1d275634193
CopilotAI review requested due to automatic review settings August 16, 2026 17:09
@github-actionsgithub-actionsBot mentioned this pull request Aug 16, 2026

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated no new comments.

@MichalPetryka

Copy link
Copy Markdown
Contributor

@MihuBot -nuget

@EgorBo

Copy link
Copy Markdown
MemberAuthor

@MihuBot -nuget

@MichalPetryka This change requires PGO data, MihuBot is basically just PrepareMethod

@EgorBo

Copy link
Copy Markdown
MemberAuthor

/azp run runtime-coreclr pgostress, runtime-coreclr libraries-pgo

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 2 pipeline(s).

@EgorBo

Copy link
Copy Markdown
MemberAuthor

@EgorBot -linux_amd -osx_arm64 -windows_amd

usingSystem.Runtime.CompilerServices;usingBenchmarkDotNet.Attributes;usingBenchmarkDotNet.Running;BenchmarkSwitcher.FromAssembly(typeof(Bench).Assembly).Run(args);publicinterfaceIOp{intDo(intx);}publicclassOpA:IOp{[MethodImpl(MethodImplOptions.NoInlining)]publicintDo(intx)=>x+1;}publicclassOpB:IOp{[MethodImpl(MethodImplOptions.NoInlining)]publicintDo(intx)=>x+2;}publicabstractclassBase{publicabstractintDo(intx);}publicclassDerA:Base{[MethodImpl(MethodImplOptions.NoInlining)]publicoverrideintDo(intx)=>x+1;}publicclassDerB:Base{[MethodImpl(MethodImplOptions.NoInlining)]publicoverrideintDo(intx)=>x+2;}publicclassBench{privateIOp[]_ifaces=newIOp[64];privateBase[]_virts=newBase[64];[GlobalSetup]publicvoidSetup(){for(inti=0;i<64;i++){// 75% A, 25% B_ifaces[i]=i%4==0?newOpB():newOpA();_virts[i]=i%4==0?newDerB():(Base)newDerA();}}[Benchmark]publicintInterfaceCall(){intsum=0;IOp[]ops=_ifaces;for(inti=0;i<ops.Length;i++)sum+=ops[i].Do(i);returnsum;}[Benchmark]publicintVirtualCall(){intsum=0;Base[]objs=_virts;for(inti=0;i<objs.Length;i++)sum+=objs[i].Do(i);returnsum;}}

@EgorBo

Copy link
Copy Markdown
MemberAuthor

PTAL @AndyAyersMS (see PR description), .NET 12.0 change cc @dotnet/jit-contrib.

@EgorBo

EgorBo commented Aug 17, 2026

Copy link
Copy Markdown
MemberAuthor

cc @MichalStrehovsky I think you asked for it a while back for NAOT.

Comment threadsrc/coreclr/jit/importercalls.cpp Outdated
Comment threadsrc/coreclr/jit/importercalls.cpp
EgorBoand others added 2 commits August 26, 2026 18:10
… revoke dump
The tail-recursion-to-loop transform in fgMorphPotentialTailCall requires a
non-virtual callee, so bailing out of GDV for recursive implicit tail calls
didn't preserve the transform - it prevented it. Keep the candidate instead.
Also restore the 'Revoking guarded devirtualization candidate' JITDUMP.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings August 26, 2026 16:30

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated 2 comments.

Comment threadsrc/coreclr/jit/inline.h
Comment threadsrc/coreclr/jit/jitconfigvalues.h
@EgorBo

Copy link
Copy Markdown
MemberAuthor

@AndyAyersMS addressed your feedback, it now needs re-approval 🙂

@EgorBo

Copy link
Copy Markdown
MemberAuthor

Ping @AndyAyersMS

@EgorBo
EgorBo enabled auto-merge (squash) September 1, 2026 11:20
@EgorBo
EgorBo merged commit 0055ffd into dotnet:mainSep 1, 2026
134 checks passed
@BrzVlad

Copy link
Copy Markdown
Member

@EgorBo I suspect this started causing crashes on my PR (#132787). Thoughts ?

 /__w/1/s/artifacts/bin/coreclr/linux.x64.Checked/x64/crossgen2/crossgen2 -o:/__w/1/s/artifacts/bin/coreclr/linux.x64.Checked/System.Private.CoreLib.dll -r:/__w/1/s/artifacts/bin/coreclr/linux.x64.Checked/IL/*.dll --targetarch:x64 --obj-format:pe --targetos:linux --target-allows-runtime-code-generation:true -m:/__w/1/s/artifacts/bin/coreclr/linux.x64.Checked/StandardOptimizationData.mibc --embed-pgo-data -O --verify-type-and-field-layout --enable-cached-interface-dispatch-support /__w/1/s/artifacts/bin/coreclr/linux.x64.Checked/IL/System.Private.CoreLib.dll --perfmap-format-version:1 --perfmap --perfmap-path:/__w/1/s/artifacts/bin/coreclr/linux.x64.Checked/
/__w/1/s/src/coreclr/jit/fgdiagnostic.cpp:3416
Assertion failed 'block->HasFlag(BBF_RECURSIVE_TAILCALL)' in 'System.Type:GetTypeCodeImpl():int:this' during 'Morph - Inlining' (IL size 23; hash 0xf87e7a03; FullOpts)

@EgorBo

EgorBo commented Sep 1, 2026

Copy link
Copy Markdown
MemberAuthor

@EgorBo I suspect this started causing crashes on my PR (#132787). Thoughts ?

 /__w/1/s/artifacts/bin/coreclr/linux.x64.Checked/x64/crossgen2/crossgen2 -o:/__w/1/s/artifacts/bin/coreclr/linux.x64.Checked/System.Private.CoreLib.dll -r:/__w/1/s/artifacts/bin/coreclr/linux.x64.Checked/IL/*.dll --targetarch:x64 --obj-format:pe --targetos:linux --target-allows-runtime-code-generation:true -m:/__w/1/s/artifacts/bin/coreclr/linux.x64.Checked/StandardOptimizationData.mibc --embed-pgo-data -O --verify-type-and-field-layout --enable-cached-interface-dispatch-support /__w/1/s/artifacts/bin/coreclr/linux.x64.Checked/IL/System.Private.CoreLib.dll --perfmap-format-version:1 --perfmap --perfmap-path:/__w/1/s/artifacts/bin/coreclr/linux.x64.Checked/
/__w/1/s/src/coreclr/jit/fgdiagnostic.cpp:3416
Assertion failed 'block->HasFlag(BBF_RECURSIVE_TAILCALL)' in 'System.Type:GetTypeCodeImpl():int:this' during 'Morph - Inlining' (IL size 23; hash 0xf87e7a03; FullOpts)

Yep, sorry, it's a bad last-minute feedback address change, I'll file a fix in a minute

@EgorBo

EgorBo commented Sep 1, 2026

Copy link
Copy Markdown
MemberAuthor

#133014

Fixed.

EgorBo added a commit that referenced this pull request Sep 1, 2026
Main is currently broken in Checked CoreCLR builds while crossgenning
`System.Private.CoreLib`:
```
Assertion failed 'block->HasFlag(BBF_RECURSIVE_TAILCALL)' in 'System.Type:GetTypeCodeImpl():int:this' during 'Morph - Inlining'
```
#132375 began retaining non-inlineable GDV candidates for implicit
recursive tail calls after [this review
discussion](#132375 (comment)).
GDV expansion then moves the residual recursive call into a new block
without its recursive-tailcall block marker, triggering the assertion.
Conservatively avoid retaining non-inlineable GDV candidates for all
tail calls. Inlineable implicit-tail-call GDV remains unchanged. We can
be less conservative, but that requires changes in the GDV expansion
code (at very least - mark blocks as containing a tail call, ensure its
shape is correct).
Validation:
- `build.cmd Clr.Jit -c Checked`
- Re-ran the failing Crossgen2 command
- SuperPMI replay of `benchmarks.run.` (56,322 contexts)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 0458833e-ff11-4cb8-9ca0-8e06f2034c2d
@EgorBo
EgorBo deleted the gdv-no-inline branch September 1, 2026 14:01
@dotnet-milestone-botdotnet-milestone-botBot added this to the 12.0-preview1 milestone Sep 2, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area-CodeGen-coreclrCLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants

@EgorBo@MichalPetryka@BrzVlad@AndyAyersMS
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Don't require GDV targets to be inlineable - #132375

Merged
EgorBo merged 5 commits into
dotnet:mainfrom
EgorBo:gdv-no-inline
Sep 1, 2026
Merged

Don't require GDV targets to be inlineable#132375
EgorBo merged 5 commits into
dotnet:mainfrom
EgorBo:gdv-no-inline

Conversation

@EgorBo

@EgorBoEgorBo commented Aug 16, 2026

Copy link
Copy Markdown
Member

GDV used to bail out when the target wasn't inlineable. A direct call still beats a virtual one, so keep the candidate and just don't inline it. DOTNET_JitGuardedDevirtualizationRequireInlining=1 restores the old behavior. Class probes only.

I know it doesn't match Andy's numbers, but many things have changed since then (e.g. CET), also, I assume we never tested it on arm64. So far it's a pure improvement on most kinds of scenarious (interface, virtual calls, etc.), see benchmarks below. We still might want to avoid doing it for virtual calls (esp for large target methods where the cost of the type check + direct call is a noise anyway) only on CoreCLR, but I propose we do it for everything and watch dotnet/performance reports.

Should help NativeAOT the most, where getExactClasses gives exact devirtualization but targets often aren't inlineable (if at least one target of getExactClasses wasn't inlineable we used to bail on the whole thing).

CoreCLR, default env, all targets [NoInlining] (so baseline does no GDV at all). Columns = how often the guessed type hits, ratio = new/base, lower is better.

BenchmarkMonoHit90Hit50Hit33Hit25
DirectControl (noise floor)1.010.991.011.011.00
Interface$\color{green}{0.72}$$\color{green}{0.48}$$\color{green}{0.45}$$\color{green}{0.64}$$\color{green}{0.56}$
InterfaceShared (__Canon)$\color{green}{0.88}$$\color{green}{0.50}$$\color{green}{0.51}$$\color{green}{0.62}$$\color{green}{0.59}$
InterfaceCostlyTarget$\color{green}{0.86}$$\color{green}{0.58}$$\color{green}{0.55}$$\color{green}{0.65}$$\color{green}{0.60}$
Virtual$\color{green}{0.86}$$\color{green}{0.78}$$\color{green}{0.63}$$\color{green}{0.64}$$\color{green}{0.62}$
VirtualShared (__Canon)$\color{red}{1.19}$$\color{green}{0.55}$$\color{green}{0.55}$$\color{green}{0.52}$$\color{green}{0.63}$
InterfaceHugeTarget (GDV in both)1.101.101.031.011.00

Only regression is monomorphic shared-generic virtual - a well-predicted vtable call is already as cheap as compare+direct call.

rt-sz report: MichalStrehovsky/rt-sz#246
smoke-test benchmarks: EgorBot/Benchmarks#489 (comment)

Guarded devirtualization used to give up whenever the target we'd devirtualize
to couldn't be inlined. But a direct call is still cheaper than a virtual or
interface call, so keep the candidate and just don't inline it.
Added JitGuardedDevirtualizationRequireInlining to get the old behavior back.
Class-based GDV only for now, method/delegate GDV is unchanged.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 38498b8d-c116-4637-b069-d1d275634193
CopilotAI lite review requested due to automatic review settings August 16, 2026 16:31
@github-actionsgithub-actionsBot added the area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI label Aug 16, 2026
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 5 pipeline(s).
11 pipeline(s) were filtered out due to trigger conditions.
There may be pipelines that require an authorized user to comment /azp run to run.

@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @JulieLeeMSFT, @jakobbotsch
See info in area-owners.md if you want to be subscribed.

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

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 adjusts CoreCLR JIT guarded devirtualization (GDV) so that class-based GDV candidates can be kept and expanded even when the devirtualized target isn’t inlineable, preserving the direct-call benefit. It also adds a config switch to restore the legacy “require inlineability” behavior and tightens up candidate bookkeeping to support the new flow.

Changes:

  • Add JitGuardedDevirtualizationRequireInlining config (default 0) to optionally restore legacy behavior of dropping non-inlineable GDV targets.
  • Track per-candidate inlineability via InlineCandidateInfo::isInlineable and keep non-inlineable class-GDV candidates for devirtualization-only.
  • Fix candidate bookkeeping (value-initialize InlineCandidateInfo and adjust spill-temp/enumerator-cloning mapping handling) to avoid relying on impCheckCanInline having run.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 1 comment.

Show a summary per file
FileDescription
src/coreclr/jit/jitmetadatalist.hAdds a new JIT metric counter (NoInlineGDV) for devirt-without-inlining cases.
src/coreclr/jit/jitconfigvalues.hIntroduces JitGuardedDevirtualizationRequireInlining config to gate legacy behavior.
src/coreclr/jit/inline.hAdds InlineCandidateInfo::isInlineable to distinguish inline vs devirt-only GDV candidates.
src/coreclr/jit/indirectcalltransformer.cppRemoves “must be inline-candidate” bail-out; expands GDV while conditionally re-marking inline candidates; fixes spill-temp and enumerator cloning map behavior.
src/coreclr/jit/importercalls.cppKeeps non-inlineable class-GDV candidates when legal; value-initializes candidate info; adjusts struct-return fixup deferral for GDV candidates.
src/coreclr/jit/compiler.hDeclares Compiler::canKeepNonInlineableGdvCandidate.

Comment threadsrc/coreclr/jit/importercalls.cpp Outdated
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 38498b8d-c116-4637-b069-d1d275634193
CopilotAI review requested due to automatic review settings August 16, 2026 16:53

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated no new comments.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 38498b8d-c116-4637-b069-d1d275634193
CopilotAI review requested due to automatic review settings August 16, 2026 17:09
@github-actionsgithub-actionsBot mentioned this pull request Aug 16, 2026

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated no new comments.

@MichalPetryka

Copy link
Copy Markdown
Contributor

@MihuBot -nuget

@EgorBo

Copy link
Copy Markdown
MemberAuthor

@MihuBot -nuget

@MichalPetryka This change requires PGO data, MihuBot is basically just PrepareMethod

@EgorBo

Copy link
Copy Markdown
MemberAuthor

/azp run runtime-coreclr pgostress, runtime-coreclr libraries-pgo

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 2 pipeline(s).

@EgorBo

Copy link
Copy Markdown
MemberAuthor

@EgorBot -linux_amd -osx_arm64 -windows_amd

usingSystem.Runtime.CompilerServices;usingBenchmarkDotNet.Attributes;usingBenchmarkDotNet.Running;BenchmarkSwitcher.FromAssembly(typeof(Bench).Assembly).Run(args);publicinterfaceIOp{intDo(intx);}publicclassOpA:IOp{[MethodImpl(MethodImplOptions.NoInlining)]publicintDo(intx)=>x+1;}publicclassOpB:IOp{[MethodImpl(MethodImplOptions.NoInlining)]publicintDo(intx)=>x+2;}publicabstractclassBase{publicabstractintDo(intx);}publicclassDerA:Base{[MethodImpl(MethodImplOptions.NoInlining)]publicoverrideintDo(intx)=>x+1;}publicclassDerB:Base{[MethodImpl(MethodImplOptions.NoInlining)]publicoverrideintDo(intx)=>x+2;}publicclassBench{privateIOp[]_ifaces=newIOp[64];privateBase[]_virts=newBase[64];[GlobalSetup]publicvoidSetup(){for(inti=0;i<64;i++){// 75% A, 25% B_ifaces[i]=i%4==0?newOpB():newOpA();_virts[i]=i%4==0?newDerB():(Base)newDerA();}}[Benchmark]publicintInterfaceCall(){intsum=0;IOp[]ops=_ifaces;for(inti=0;i<ops.Length;i++)sum+=ops[i].Do(i);returnsum;}[Benchmark]publicintVirtualCall(){intsum=0;Base[]objs=_virts;for(inti=0;i<objs.Length;i++)sum+=objs[i].Do(i);returnsum;}}

@EgorBo

Copy link
Copy Markdown
MemberAuthor

PTAL @AndyAyersMS (see PR description), .NET 12.0 change cc @dotnet/jit-contrib.

@EgorBo

EgorBo commented Aug 17, 2026

Copy link
Copy Markdown
MemberAuthor

cc @MichalStrehovsky I think you asked for it a while back for NAOT.

Comment threadsrc/coreclr/jit/importercalls.cpp Outdated
Comment threadsrc/coreclr/jit/importercalls.cpp
EgorBoand others added 2 commits August 26, 2026 18:10
… revoke dump
The tail-recursion-to-loop transform in fgMorphPotentialTailCall requires a
non-virtual callee, so bailing out of GDV for recursive implicit tail calls
didn't preserve the transform - it prevented it. Keep the candidate instead.
Also restore the 'Revoking guarded devirtualization candidate' JITDUMP.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings August 26, 2026 16:30

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated 2 comments.

Comment threadsrc/coreclr/jit/inline.h
Comment threadsrc/coreclr/jit/jitconfigvalues.h
@EgorBo

Copy link
Copy Markdown
MemberAuthor

@AndyAyersMS addressed your feedback, it now needs re-approval 🙂

@EgorBo

Copy link
Copy Markdown
MemberAuthor

Ping @AndyAyersMS

@EgorBo
EgorBo enabled auto-merge (squash) September 1, 2026 11:20
@EgorBo
EgorBo merged commit 0055ffd into dotnet:mainSep 1, 2026
134 checks passed
@BrzVlad

Copy link
Copy Markdown
Member

@EgorBo I suspect this started causing crashes on my PR (#132787). Thoughts ?

 /__w/1/s/artifacts/bin/coreclr/linux.x64.Checked/x64/crossgen2/crossgen2 -o:/__w/1/s/artifacts/bin/coreclr/linux.x64.Checked/System.Private.CoreLib.dll -r:/__w/1/s/artifacts/bin/coreclr/linux.x64.Checked/IL/*.dll --targetarch:x64 --obj-format:pe --targetos:linux --target-allows-runtime-code-generation:true -m:/__w/1/s/artifacts/bin/coreclr/linux.x64.Checked/StandardOptimizationData.mibc --embed-pgo-data -O --verify-type-and-field-layout --enable-cached-interface-dispatch-support /__w/1/s/artifacts/bin/coreclr/linux.x64.Checked/IL/System.Private.CoreLib.dll --perfmap-format-version:1 --perfmap --perfmap-path:/__w/1/s/artifacts/bin/coreclr/linux.x64.Checked/
/__w/1/s/src/coreclr/jit/fgdiagnostic.cpp:3416
Assertion failed 'block->HasFlag(BBF_RECURSIVE_TAILCALL)' in 'System.Type:GetTypeCodeImpl():int:this' during 'Morph - Inlining' (IL size 23; hash 0xf87e7a03; FullOpts)

@EgorBo

EgorBo commented Sep 1, 2026

Copy link
Copy Markdown
MemberAuthor

@EgorBo I suspect this started causing crashes on my PR (#132787). Thoughts ?

 /__w/1/s/artifacts/bin/coreclr/linux.x64.Checked/x64/crossgen2/crossgen2 -o:/__w/1/s/artifacts/bin/coreclr/linux.x64.Checked/System.Private.CoreLib.dll -r:/__w/1/s/artifacts/bin/coreclr/linux.x64.Checked/IL/*.dll --targetarch:x64 --obj-format:pe --targetos:linux --target-allows-runtime-code-generation:true -m:/__w/1/s/artifacts/bin/coreclr/linux.x64.Checked/StandardOptimizationData.mibc --embed-pgo-data -O --verify-type-and-field-layout --enable-cached-interface-dispatch-support /__w/1/s/artifacts/bin/coreclr/linux.x64.Checked/IL/System.Private.CoreLib.dll --perfmap-format-version:1 --perfmap --perfmap-path:/__w/1/s/artifacts/bin/coreclr/linux.x64.Checked/
/__w/1/s/src/coreclr/jit/fgdiagnostic.cpp:3416
Assertion failed 'block->HasFlag(BBF_RECURSIVE_TAILCALL)' in 'System.Type:GetTypeCodeImpl():int:this' during 'Morph - Inlining' (IL size 23; hash 0xf87e7a03; FullOpts)

Yep, sorry, it's a bad last-minute feedback address change, I'll file a fix in a minute

@EgorBo

EgorBo commented Sep 1, 2026

Copy link
Copy Markdown
MemberAuthor

#133014

Fixed.

EgorBo added a commit that referenced this pull request Sep 1, 2026
Main is currently broken in Checked CoreCLR builds while crossgenning
`System.Private.CoreLib`:
```
Assertion failed 'block->HasFlag(BBF_RECURSIVE_TAILCALL)' in 'System.Type:GetTypeCodeImpl():int:this' during 'Morph - Inlining'
```
#132375 began retaining non-inlineable GDV candidates for implicit
recursive tail calls after [this review
discussion](#132375 (comment)).
GDV expansion then moves the residual recursive call into a new block
without its recursive-tailcall block marker, triggering the assertion.
Conservatively avoid retaining non-inlineable GDV candidates for all
tail calls. Inlineable implicit-tail-call GDV remains unchanged. We can
be less conservative, but that requires changes in the GDV expansion
code (at very least - mark blocks as containing a tail call, ensure its
shape is correct).
Validation:
- `build.cmd Clr.Jit -c Checked`
- Re-ran the failing Crossgen2 command
- SuperPMI replay of `benchmarks.run.` (56,322 contexts)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 0458833e-ff11-4cb8-9ca0-8e06f2034c2d
@EgorBo
EgorBo deleted the gdv-no-inline branch September 1, 2026 14:01
@dotnet-milestone-botdotnet-milestone-botBot added this to the 12.0-preview1 milestone Sep 2, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area-CodeGen-coreclrCLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants

@EgorBo@MichalPetryka@BrzVlad@AndyAyersMS
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Don't require GDV targets to be inlineable - #132375

Merged
EgorBo merged 5 commits into
dotnet:mainfrom
EgorBo:gdv-no-inline
Sep 1, 2026
Merged

Don't require GDV targets to be inlineable#132375
EgorBo merged 5 commits into
dotnet:mainfrom
EgorBo:gdv-no-inline

Conversation

@EgorBo

@EgorBoEgorBo commented Aug 16, 2026

Copy link
Copy Markdown
Member

GDV used to bail out when the target wasn't inlineable. A direct call still beats a virtual one, so keep the candidate and just don't inline it. DOTNET_JitGuardedDevirtualizationRequireInlining=1 restores the old behavior. Class probes only.

I know it doesn't match Andy's numbers, but many things have changed since then (e.g. CET), also, I assume we never tested it on arm64. So far it's a pure improvement on most kinds of scenarious (interface, virtual calls, etc.), see benchmarks below. We still might want to avoid doing it for virtual calls (esp for large target methods where the cost of the type check + direct call is a noise anyway) only on CoreCLR, but I propose we do it for everything and watch dotnet/performance reports.

Should help NativeAOT the most, where getExactClasses gives exact devirtualization but targets often aren't inlineable (if at least one target of getExactClasses wasn't inlineable we used to bail on the whole thing).

CoreCLR, default env, all targets [NoInlining] (so baseline does no GDV at all). Columns = how often the guessed type hits, ratio = new/base, lower is better.

BenchmarkMonoHit90Hit50Hit33Hit25
DirectControl (noise floor)1.010.991.011.011.00
Interface$\color{green}{0.72}$$\color{green}{0.48}$$\color{green}{0.45}$$\color{green}{0.64}$$\color{green}{0.56}$
InterfaceShared (__Canon)$\color{green}{0.88}$$\color{green}{0.50}$$\color{green}{0.51}$$\color{green}{0.62}$$\color{green}{0.59}$
InterfaceCostlyTarget$\color{green}{0.86}$$\color{green}{0.58}$$\color{green}{0.55}$$\color{green}{0.65}$$\color{green}{0.60}$
Virtual$\color{green}{0.86}$$\color{green}{0.78}$$\color{green}{0.63}$$\color{green}{0.64}$$\color{green}{0.62}$
VirtualShared (__Canon)$\color{red}{1.19}$$\color{green}{0.55}$$\color{green}{0.55}$$\color{green}{0.52}$$\color{green}{0.63}$
InterfaceHugeTarget (GDV in both)1.101.101.031.011.00

Only regression is monomorphic shared-generic virtual - a well-predicted vtable call is already as cheap as compare+direct call.

rt-sz report: MichalStrehovsky/rt-sz#246
smoke-test benchmarks: EgorBot/Benchmarks#489 (comment)

Guarded devirtualization used to give up whenever the target we'd devirtualize
to couldn't be inlined. But a direct call is still cheaper than a virtual or
interface call, so keep the candidate and just don't inline it.
Added JitGuardedDevirtualizationRequireInlining to get the old behavior back.
Class-based GDV only for now, method/delegate GDV is unchanged.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 38498b8d-c116-4637-b069-d1d275634193
CopilotAI lite review requested due to automatic review settings August 16, 2026 16:31
@github-actionsgithub-actionsBot added the area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI label Aug 16, 2026
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 5 pipeline(s).
11 pipeline(s) were filtered out due to trigger conditions.
There may be pipelines that require an authorized user to comment /azp run to run.

@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @JulieLeeMSFT, @jakobbotsch
See info in area-owners.md if you want to be subscribed.

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

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 adjusts CoreCLR JIT guarded devirtualization (GDV) so that class-based GDV candidates can be kept and expanded even when the devirtualized target isn’t inlineable, preserving the direct-call benefit. It also adds a config switch to restore the legacy “require inlineability” behavior and tightens up candidate bookkeeping to support the new flow.

Changes:

  • Add JitGuardedDevirtualizationRequireInlining config (default 0) to optionally restore legacy behavior of dropping non-inlineable GDV targets.
  • Track per-candidate inlineability via InlineCandidateInfo::isInlineable and keep non-inlineable class-GDV candidates for devirtualization-only.
  • Fix candidate bookkeeping (value-initialize InlineCandidateInfo and adjust spill-temp/enumerator-cloning mapping handling) to avoid relying on impCheckCanInline having run.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 1 comment.

Show a summary per file
FileDescription
src/coreclr/jit/jitmetadatalist.hAdds a new JIT metric counter (NoInlineGDV) for devirt-without-inlining cases.
src/coreclr/jit/jitconfigvalues.hIntroduces JitGuardedDevirtualizationRequireInlining config to gate legacy behavior.
src/coreclr/jit/inline.hAdds InlineCandidateInfo::isInlineable to distinguish inline vs devirt-only GDV candidates.
src/coreclr/jit/indirectcalltransformer.cppRemoves “must be inline-candidate” bail-out; expands GDV while conditionally re-marking inline candidates; fixes spill-temp and enumerator cloning map behavior.
src/coreclr/jit/importercalls.cppKeeps non-inlineable class-GDV candidates when legal; value-initializes candidate info; adjusts struct-return fixup deferral for GDV candidates.
src/coreclr/jit/compiler.hDeclares Compiler::canKeepNonInlineableGdvCandidate.

Comment threadsrc/coreclr/jit/importercalls.cpp Outdated
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 38498b8d-c116-4637-b069-d1d275634193
CopilotAI review requested due to automatic review settings August 16, 2026 16:53

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated no new comments.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 38498b8d-c116-4637-b069-d1d275634193
CopilotAI review requested due to automatic review settings August 16, 2026 17:09
@github-actionsgithub-actionsBot mentioned this pull request Aug 16, 2026

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated no new comments.

@MichalPetryka

Copy link
Copy Markdown
Contributor

@MihuBot -nuget

@EgorBo

Copy link
Copy Markdown
MemberAuthor

@MihuBot -nuget

@MichalPetryka This change requires PGO data, MihuBot is basically just PrepareMethod

@EgorBo

Copy link
Copy Markdown
MemberAuthor

/azp run runtime-coreclr pgostress, runtime-coreclr libraries-pgo

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 2 pipeline(s).

@EgorBo

Copy link
Copy Markdown
MemberAuthor

@EgorBot -linux_amd -osx_arm64 -windows_amd

usingSystem.Runtime.CompilerServices;usingBenchmarkDotNet.Attributes;usingBenchmarkDotNet.Running;BenchmarkSwitcher.FromAssembly(typeof(Bench).Assembly).Run(args);publicinterfaceIOp{intDo(intx);}publicclassOpA:IOp{[MethodImpl(MethodImplOptions.NoInlining)]publicintDo(intx)=>x+1;}publicclassOpB:IOp{[MethodImpl(MethodImplOptions.NoInlining)]publicintDo(intx)=>x+2;}publicabstractclassBase{publicabstractintDo(intx);}publicclassDerA:Base{[MethodImpl(MethodImplOptions.NoInlining)]publicoverrideintDo(intx)=>x+1;}publicclassDerB:Base{[MethodImpl(MethodImplOptions.NoInlining)]publicoverrideintDo(intx)=>x+2;}publicclassBench{privateIOp[]_ifaces=newIOp[64];privateBase[]_virts=newBase[64];[GlobalSetup]publicvoidSetup(){for(inti=0;i<64;i++){// 75% A, 25% B_ifaces[i]=i%4==0?newOpB():newOpA();_virts[i]=i%4==0?newDerB():(Base)newDerA();}}[Benchmark]publicintInterfaceCall(){intsum=0;IOp[]ops=_ifaces;for(inti=0;i<ops.Length;i++)sum+=ops[i].Do(i);returnsum;}[Benchmark]publicintVirtualCall(){intsum=0;Base[]objs=_virts;for(inti=0;i<objs.Length;i++)sum+=objs[i].Do(i);returnsum;}}

@EgorBo

Copy link
Copy Markdown
MemberAuthor

PTAL @AndyAyersMS (see PR description), .NET 12.0 change cc @dotnet/jit-contrib.

@EgorBo

EgorBo commented Aug 17, 2026

Copy link
Copy Markdown
MemberAuthor

cc @MichalStrehovsky I think you asked for it a while back for NAOT.

Comment threadsrc/coreclr/jit/importercalls.cpp Outdated
Comment threadsrc/coreclr/jit/importercalls.cpp
EgorBoand others added 2 commits August 26, 2026 18:10
… revoke dump
The tail-recursion-to-loop transform in fgMorphPotentialTailCall requires a
non-virtual callee, so bailing out of GDV for recursive implicit tail calls
didn't preserve the transform - it prevented it. Keep the candidate instead.
Also restore the 'Revoking guarded devirtualization candidate' JITDUMP.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings August 26, 2026 16:30

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated 2 comments.

Comment threadsrc/coreclr/jit/inline.h
Comment threadsrc/coreclr/jit/jitconfigvalues.h
@EgorBo

Copy link
Copy Markdown
MemberAuthor

@AndyAyersMS addressed your feedback, it now needs re-approval 🙂

@EgorBo

Copy link
Copy Markdown
MemberAuthor

Ping @AndyAyersMS

@EgorBo
EgorBo enabled auto-merge (squash) September 1, 2026 11:20
@EgorBo
EgorBo merged commit 0055ffd into dotnet:mainSep 1, 2026
134 checks passed
@BrzVlad

Copy link
Copy Markdown
Member

@EgorBo I suspect this started causing crashes on my PR (#132787). Thoughts ?

 /__w/1/s/artifacts/bin/coreclr/linux.x64.Checked/x64/crossgen2/crossgen2 -o:/__w/1/s/artifacts/bin/coreclr/linux.x64.Checked/System.Private.CoreLib.dll -r:/__w/1/s/artifacts/bin/coreclr/linux.x64.Checked/IL/*.dll --targetarch:x64 --obj-format:pe --targetos:linux --target-allows-runtime-code-generation:true -m:/__w/1/s/artifacts/bin/coreclr/linux.x64.Checked/StandardOptimizationData.mibc --embed-pgo-data -O --verify-type-and-field-layout --enable-cached-interface-dispatch-support /__w/1/s/artifacts/bin/coreclr/linux.x64.Checked/IL/System.Private.CoreLib.dll --perfmap-format-version:1 --perfmap --perfmap-path:/__w/1/s/artifacts/bin/coreclr/linux.x64.Checked/
/__w/1/s/src/coreclr/jit/fgdiagnostic.cpp:3416
Assertion failed 'block->HasFlag(BBF_RECURSIVE_TAILCALL)' in 'System.Type:GetTypeCodeImpl():int:this' during 'Morph - Inlining' (IL size 23; hash 0xf87e7a03; FullOpts)

@EgorBo

EgorBo commented Sep 1, 2026

Copy link
Copy Markdown
MemberAuthor

@EgorBo I suspect this started causing crashes on my PR (#132787). Thoughts ?

 /__w/1/s/artifacts/bin/coreclr/linux.x64.Checked/x64/crossgen2/crossgen2 -o:/__w/1/s/artifacts/bin/coreclr/linux.x64.Checked/System.Private.CoreLib.dll -r:/__w/1/s/artifacts/bin/coreclr/linux.x64.Checked/IL/*.dll --targetarch:x64 --obj-format:pe --targetos:linux --target-allows-runtime-code-generation:true -m:/__w/1/s/artifacts/bin/coreclr/linux.x64.Checked/StandardOptimizationData.mibc --embed-pgo-data -O --verify-type-and-field-layout --enable-cached-interface-dispatch-support /__w/1/s/artifacts/bin/coreclr/linux.x64.Checked/IL/System.Private.CoreLib.dll --perfmap-format-version:1 --perfmap --perfmap-path:/__w/1/s/artifacts/bin/coreclr/linux.x64.Checked/
/__w/1/s/src/coreclr/jit/fgdiagnostic.cpp:3416
Assertion failed 'block->HasFlag(BBF_RECURSIVE_TAILCALL)' in 'System.Type:GetTypeCodeImpl():int:this' during 'Morph - Inlining' (IL size 23; hash 0xf87e7a03; FullOpts)

Yep, sorry, it's a bad last-minute feedback address change, I'll file a fix in a minute

@EgorBo

EgorBo commented Sep 1, 2026

Copy link
Copy Markdown
MemberAuthor

#133014

Fixed.

EgorBo added a commit that referenced this pull request Sep 1, 2026
Main is currently broken in Checked CoreCLR builds while crossgenning
`System.Private.CoreLib`:
```
Assertion failed 'block->HasFlag(BBF_RECURSIVE_TAILCALL)' in 'System.Type:GetTypeCodeImpl():int:this' during 'Morph - Inlining'
```
#132375 began retaining non-inlineable GDV candidates for implicit
recursive tail calls after [this review
discussion](#132375 (comment)).
GDV expansion then moves the residual recursive call into a new block
without its recursive-tailcall block marker, triggering the assertion.
Conservatively avoid retaining non-inlineable GDV candidates for all
tail calls. Inlineable implicit-tail-call GDV remains unchanged. We can
be less conservative, but that requires changes in the GDV expansion
code (at very least - mark blocks as containing a tail call, ensure its
shape is correct).
Validation:
- `build.cmd Clr.Jit -c Checked`
- Re-ran the failing Crossgen2 command
- SuperPMI replay of `benchmarks.run.` (56,322 contexts)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 0458833e-ff11-4cb8-9ca0-8e06f2034c2d
@EgorBo
EgorBo deleted the gdv-no-inline branch September 1, 2026 14:01
@dotnet-milestone-botdotnet-milestone-botBot added this to the 12.0-preview1 milestone Sep 2, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area-CodeGen-coreclrCLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants

@EgorBo@MichalPetryka@BrzVlad@AndyAyersMS