Skip to content

Conversation

@hez2010
Copy link
Contributor

@hez2010 hez2010 commented Oct 18, 2025

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

Copilot AI review requested due to automatic review settings October 18, 2025 13:46
@dotnet-policy-service dotnet-policy-service bot added the community-contribution Indicates that the PR has been added by a community member label Oct 18, 2025
@github-actions github-actions bot added the area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI label Oct 18, 2025
Copy link
Contributor

Copilot AI left a comment

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
File Description
src/coreclr/vm/jitinterface.cpp Updated field assignments from wasArrayInterfaceDevirt to needsMethodContext
src/coreclr/tools/superpmi/superpmi-shared/methodcontext.cpp Updated SuperPMI recording/replay to use the renamed field
src/coreclr/tools/superpmi/superpmi-shared/agnostic.h Renamed struct field from wasArrayInterfaceDevirt to needsMethodContext
src/coreclr/jit/importercalls.cpp Removed function pointer spilling, improved "this" pointer handling, updated field references
src/coreclr/jit/compiler.hpp Fixed comment for helper function
src/coreclr/inc/corinfo.h Updated struct definition and comments to use the new field name

@dotnet-policy-service
Copy link
Contributor

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

@hez2010 hez2010 force-pushed the ldvirtftn-no-spill branch from ee4bd19 to 1c781c1 Compare October 18, 2025 13:54
@hez2010 hez2010 force-pushed the ldvirtftn-no-spill branch from bc516bb to a2a1d86 Compare October 18, 2025 17:27
@hez2010
Copy link
Contributor Author

hez2010 commented Oct 19, 2025

trigger mihubot since spmi jobs are broken @MihuBot

@EgorBo
Copy link
Member

EgorBo commented Oct 19, 2025

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.

using System.Runtime.CompilerServices;

for (int i = 0; i < 200; i++)
{
    Test(new MyValueProcessor());
    Thread.Sleep(16);
}


[MethodImpl(MethodImplOptions.NoInlining)]
static void Test(IProcessor p)
{
    p.Process(42); // should devirtualize, but doesn't today.
}

public interface IProcessor
{
    void Process<T>(T item);
}

public class MyValueProcessor : IProcessor
{
    public void Process<T>(T item)
    {
        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

@EgorBo EgorBo marked this pull request as draft November 10, 2025 11:22
@hez2010
Copy link
Contributor Author

hez2010 commented Nov 17, 2025

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

@MichalStrehovsky
Copy link
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

Azure Pipelines successfully started running 1 pipeline(s).

@jakobbotsch
Copy link
Member

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

@hez2010
Copy link
Contributor Author

hez2010 commented Nov 18, 2025

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
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
Member

/azp run jit-cfg

@azure-pipelines
Copy link

Azure Pipelines successfully started running 1 pipeline(s).

@jakobbotsch
Copy link
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.

Copy link
Member

@AndyAyersMS AndyAyersMS left a comment

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?

@jakobbotsch
Copy link
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:main Nov 27, 2025
116 of 118 checks passed
@jakobbotsch
Copy link
Member

Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI community-contribution Indicates 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

6 participants