-
Notifications
You must be signed in to change notification settings - Fork 5.1k
JIT: extend indirect virtual stub optimization to arrays #116771
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
JIT: extend indirect virtual stub optimization to arrays #116771
Conversation
Add support for array interface methods invoked via indirect virtual stubs. Trim out a layer of temps in the importer. Remember the base method handle and use that for detecting the GetEnumerator pattern and for array interface method devirtualization. Mark the SZGenericArrayEnumerator ctor as aggressively inlined; when inlined we can often prove the enumerator (conditionally) doesn't escape. Part of dotnet#108913
Tagging subscribers to this area: @JulieLeeMSFT, @jakobbotsch |
@EgorBot -intel -arm64 using System.Collections.Generic;
using System.Runtime.CompilerServices;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
[GenericTypeArguments(typeof(string))]
[MemoryDiagnoser]
public class A<T>
{
private IEnumerable<T> _ienumerable;
[GlobalSetup(Target = nameof(IEnumerable))]
public void SetupIEnumerable() => _ienumerable = new string[512] as IEnumerable<T>;
[Benchmark]
public T IEnumerable() => Get(_ienumerable);
[MethodImpl(MethodImplOptions.NoInlining)]
private T Get(IEnumerable<T> collection)
{
T result = default;
foreach (var item in collection)
result = item;
return result;
}
private IEnumerable<string> _ienumerable_str;
[GlobalSetup(Target = nameof(IEnumerable_str))]
public void SetupIEnumerable_str() => _ienumerable_str = new string[512];
[Benchmark]
public string IEnumerable_str() => Get_str(_ienumerable_str);
[MethodImpl(MethodImplOptions.NoInlining)]
private string Get_str(IEnumerable<string> collection)
{
string result = default;
foreach (var item in collection)
result = item;
return result;
}
} |
Two failures that I spotted in scanning: Unexpected NREs
A runtime assert
|
For the assert (here looking at the span Indexer test) we have this tree:
We try late devirt, and need the type of The devirt is to an instantiating stub, so we end up producing
and this context is the wrong type. So we do the wrong instantiation...(?) and blow up later trying to invoke Maybe in array interface cases if the object (array) type is inexact we need to just bail out on devirtualization... let me see how messy that ends up being. |
@EgorBot -intel -arm64 using System.Collections.Generic;
using System.Runtime.CompilerServices;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
[GenericTypeArguments(typeof(string))]
[MemoryDiagnoser]
public class A<T>
{
private IEnumerable<T> _ienumerable;
[GlobalSetup(Target = nameof(IEnumerable))]
public void SetupIEnumerable() => _ienumerable = new string[512] as IEnumerable<T>;
[Benchmark]
public T IEnumerable() => Get(_ienumerable);
[MethodImpl(MethodImplOptions.NoInlining)]
private T Get(IEnumerable<T> collection)
{
T result = default;
foreach (var item in collection)
result = item;
return result;
}
private IEnumerable<string> _ienumerable_str;
[GlobalSetup(Target = nameof(IEnumerable_str))]
public void SetupIEnumerable_str() => _ienumerable_str = new string[512];
[Benchmark]
public string IEnumerable_str() => Get_str(_ienumerable_str);
[MethodImpl(MethodImplOptions.NoInlining)]
private string Get_str(IEnumerable<string> collection)
{
string result = default;
foreach (var item in collection)
result = item;
return result;
}
} |
@EgorBot --filter "System.Collections.IterateForEach*.Array" |
There was a problem hiding this 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 extends indirect virtual stub optimization to handle array interface methods, reduces temporary spills in the importer, and tracks the original base method handle for GetEnumerator pattern detection. It also marks the SZGenericArrayEnumerator
constructor as aggressively inlined.
- Added
[MethodImpl(AggressiveInlining)]
to the array enumerator constructor. - Enhanced
CEEInfo::resolveVirtualMethodHelper
to support array interface devirtualization with exact element types. - Propagated an
originalMethodHandle
through JIT inlining and import phases; trimmed a layer of temps inimpImportCall
.
Reviewed Changes
Copilot reviewed 8 out of 8 changed files in this pull request and generated no comments.
Show a summary per file
File | Description |
---|---|
src/libraries/System.Private.CoreLib/src/System/Array.Enumerators.cs | Marked SZGenericArrayEnumerator ctor as aggressively inlined |
src/coreclr/vm/jitinterface.cpp | Added array-specific devirtualization path and refined interface/type checks |
src/coreclr/jit/inline.h | Introduced originalMethodHandle field in InlineCandidateInfo |
src/coreclr/jit/indirectcalltransformer.cpp | Switched to inlineInfo->originalMethodHandle for array calls |
src/coreclr/jit/importercalls.cpp | Skipped redundant temp spill for local vars; updated addGuardedDevirtualizationCandidate signature; added exact‐type bailout |
src/coreclr/jit/importer.cpp | Used originalMethodHandle for intrinsic lookup in GDV candidates |
src/coreclr/jit/fginline.cpp | Added JITDUMP tracing around method-handle resolution |
src/coreclr/jit/compiler.h | Updated addGuardedDevirtualizationCandidate declaration to include originalMethodHandle |
Comments suppressed due to low confidence (3)
src/coreclr/vm/jitinterface.cpp:8624
- [nitpick] This comment is misleading, since the following
HasInstantiation
check filters out generic interfaces rather than determining array support. Please update the comment to accurately describe the intent of the check.
// See if this interface is one of the ones arrays implement.
src/coreclr/jit/indirectcalltransformer.cpp:976
- [nitpick] Consider adding an assertion or null‐check to ensure
inlineInfo->originalMethodHandle
is valid before using it, to prevent unexpected null dereferences.
methodHnd = inlineInfo->originalMethodHandle;
src/coreclr/jit/importer.cpp:6952
GetGDVCandidateInfo(0)
may return null ororiginalMethodHandle
might be uninitialized. Please add a check thatinfo
is non-null and thatinfo->originalMethodHandle
is set before passing it tolookupNamedIntrinsic
.
InlineCandidateInfo* const info = call->GetGDVCandidateInfo(0);
@davidwrighton @dotnet/jit-contrib PTAL This mainly benefits PGO.... the case from #108913 (comment) where we're in a shared method and the underlying collection is nominally a shared array. EgorBot/runtime-utils#401 (comment) shows the improvement in that case. |
SPMI diffs are not going to give much insight here, though the elimination of one temp (in many cases) in the importer saves some size for minopts/tier0. I will do another bespoke asp.net SPMI run to get a better idea how many methods this impacts, but I expect it will be fairly small, seeing as there were only 400 or so missed contexts. |
We don't need to spill the stub address if it's already a local. Peeled off from dotnet#116771.
@EgorBot --filter "System.Collections.IterateForEach*.IEnumerable" |
@EgorBot --filter "System.Collections.IterateForEach*.IEnumerable" |
…bDevirt' into ArrayInterfaceIndirectVirtualStubDevirt
@EgorBot --filter "System.Collections.IterateForEach*.IEnumerable" |
@EgorBot --filter "System.Collections.IterateForEach*.IEnumerable" |
@davidwrighton @dotnet/jit-contrib think this is finally in good shape. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Few nits, otherwise LGTM
Co-authored-by: Aman Khalid <amankhalid@microsoft.com>
Add support for array interface methods invoked via indirect virtual stubs. Trim out a layer of temps in the importer. Remember the base method handle and use that for detecting the GetEnumerator pattern and for array interface method devirtualization. Mark the SZGenericArrayEnumerator ctor as aggressively inlined; when inlined we can often prove the enumerator (conditionally) doesn't escape.
Part of #108913