Skip to content

Stop spilling ldvirtftn - #120866

Merged
jakobbotsch merged 22 commits into
dotnet:mainfrom
hez2010:ldvirtftn-no-spill
Nov 27, 2025
Merged

Stop spilling ldvirtftn#120866
jakobbotsch merged 22 commits into
dotnet:mainfrom
hez2010:ldvirtftn-no-spill

Conversation

@hez2010

@hez2010hez2010 commented Oct 18, 2025

Copy link
Copy Markdown
Contributor

We first need to stop spilling ldvirtftn before we can take the next step on GVM devirt.
Let's stop spilling them and see what we need to take to handle potential regressions.

Contributes to #112596

Fixes#121711

CopilotAI review requested due to automatic review settings October 18, 2025 13:46
@dotnet-policy-servicedotnet-policy-serviceBot added the community-contribution Indicates that the PR has been added by a community member label Oct 18, 2025
@github-actionsgithub-actionsBot added the area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI label Oct 18, 2025

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 refactors devirtualization handling by renaming a field and removing function pointer spilling for ldvirtftn operations. The change aims to improve performance by avoiding unnecessary temporary storage while preparing for further Generic Virtual Method (GVM) devirtualization improvements.

Key changes:

  • Renamed wasArrayInterfaceDevirt to needsMethodContext to better reflect its broader purpose
  • Removed spilling of ldvirtftn function pointers to temporary locals
  • Added better handling for cloned "this" pointers in virtual function calls

Reviewed Changes

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

Show a summary per file
FileDescription
src/coreclr/vm/jitinterface.cppUpdated field assignments from wasArrayInterfaceDevirt to needsMethodContext
src/coreclr/tools/superpmi/superpmi-shared/methodcontext.cppUpdated SuperPMI recording/replay to use the renamed field
src/coreclr/tools/superpmi/superpmi-shared/agnostic.hRenamed struct field from wasArrayInterfaceDevirt to needsMethodContext
src/coreclr/jit/importercalls.cppRemoved function pointer spilling, improved "this" pointer handling, updated field references
src/coreclr/jit/compiler.hppFixed comment for helper function
src/coreclr/inc/corinfo.hUpdated struct definition and comments to use the new field name

Comment threadsrc/coreclr/jit/importercalls.cpp
Comment threadsrc/coreclr/jit/importercalls.cpp Outdated
Comment threadsrc/coreclr/inc/corinfo.h Outdated
@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.

Comment threadsrc/coreclr/jit/importercalls.cpp
@hez2010

Copy link
Copy Markdown
ContributorAuthor

trigger mihubot since spmi jobs are broken @MihuBot

@EgorBo

EgorBo commented Oct 19, 2025

Copy link
Copy Markdown
Member

It looks like what you're trying to achieve is the devirtualization of indirect calls (GVM specifically), but you seems to be focused on a very specific case where the actual type is created right before the call (judging by your example in #112596 and the fact that you try to avoid spills).

I think we should focus on implementing #44610 for PGO first, e.g.

usingSystem.Runtime.CompilerServices;for(inti=0;i<200;i++){Test(newMyValueProcessor());Thread.Sleep(16);}[MethodImpl(MethodImplOptions.NoInlining)]staticvoidTest(IProcessorp){p.Process(42);// should devirtualize, but doesn't today.}publicinterfaceIProcessor{voidProcess<T>(Titem);}publicclassMyValueProcessor:IProcessor{publicvoidProcess<T>(Titem){Console.WriteLine(item?.ToString());}}

today we give up on instrumenting indirect calls.

And then, if you also want to have it divirtualized without the PGO (e.g. NAOT) you can just create fake (speculative) GDVs (which then will be folded once we hit a phase that can see through locals). Instead of just un-spilling the target that might be changed by the args?

cc @AndyAyersMS @dotnet/jit-contrib

Comment threadsrc/coreclr/jit/lower.cpp
@EgorBo
EgorBo marked this pull request as draft November 10, 2025 11:22
@hez2010

hez2010 commented Nov 17, 2025

Copy link
Copy Markdown
ContributorAuthor

Test failure is #105124. Other than this CI is passing.
Change this PR to ready for review.

@MichalStrehovsky

Copy link
Copy Markdown
Member

/azp run runtime-nativeaot-outerloop

@hez2010
hez2010 marked this pull request as ready for review November 17, 2025 09:49
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines successfully started running 1 pipeline(s).

Comment threadsrc/coreclr/jit/importercalls.cpp Outdated
@jakobbotsch

Copy link
Copy Markdown
Member

I will take a look at the x86 asserts when I have some time.

@hez2010

Copy link
Copy Markdown
ContributorAuthor

btw I guess we may want to stop spilling the call target for CORINFO_VIRTUALCALL_STUB, CORINFO_CALL_CODE_POINTER and impImportIndirectCall as well?

@jakobbotsch

Copy link
Copy Markdown
Member

btw I guess we may want to stop spilling the call target for CORINFO_VIRTUALCALL_STUB, CORINFO_CALL_CODE_POINTER and impImportIndirectCall as well?

I think it will require its own evaluation of diffs and extending the optimization. I would leave it for a separate change.

@jakobbotsch

Copy link
Copy Markdown
Member

/azp run jit-cfg

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines successfully started running 1 pipeline(s).

@jakobbotsch

Copy link
Copy Markdown
Member

cc @dotnet/jit-contrib PTAL @AndyAyersMS@EgorBo

This PR stops spilling the target for calls implemented via ldvirtftn in the importer, which is the way we implement GVMs. We were previously spilling the target with CHECK_SPILL_NONE, but this is incorrect because it induces a null check (see #121711). So this PR fixes that issue. Also, avoiding the spill gives us a simple uncontroversial way to devirtualize in some cases when we can extract information directly from gtCallAddr.

The problem with removing the spill is code size regressions. gtCallAddr is normally evaluated after arguments, and the backend is not good at handling the ABI constraints of the arguments when gtCallAddr contains calls. To counteract that we introduce a new optimize in lowering. The optimization tries to move the evaluation of the target to happen before the arguments. It effectively accomplishes what the previous CHECK_SPILL_NONE spill was doing, except with the right legality checks.

Diffs.
A mix of code size improvements/regressions. I think the regressions are small enough to take it as is. I have also left the optimization enabled in tier0 as it does not seem expensive and it helps LSRA significantly when it kicks in.

Comment threadsrc/coreclr/jit/importercalls.cpp Outdated
Comment threadsrc/coreclr/jit/lower.cpp

@AndyAyersMSAndyAyersMS left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Generally LGTM.

Are we able to CSE or hoist these lookups out of loops?

Comment threadsrc/coreclr/jit/lower.cpp
@jakobbotsch

Copy link
Copy Markdown
Member

Are we able to CSE or hoist these lookups out of loops?

Doesn't look like it. We don't have an entry for CORINFO_HELP_VIRTUAL_FUNC_PTR in HelperCallProperties::init at all, so we think it mutates the heap. Which I guess it technically does since it fills various caches in VirtualDispatchHelpers.VirtalFunctionPointer. But I suppose it's the kind of heap mutation that we are ok with ignoring. I will submit a follow up about this.

@jakobbotsch
jakobbotsch merged commit 80d3434 into dotnet:mainNov 27, 2025
116 of 118 checks passed
@jakobbotsch

Copy link
Copy Markdown
Member

Thanks!

Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

area-CodeGen-coreclrCLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMIcommunity-contributionIndicates that the PR has been added by a community member

Projects

None yet

Development

Successfully merging this pull request may close these issues.

JIT: Illegal reordering of null check with argument evaluation for GVMs

7 participants

@hez2010@EgorBo@MichalStrehovsky@jakobbotsch@AndyAyersMS@SingleAccretion