Skip to content

Avoid resolving direct awaits to thunks (IL scanner part)#124536

Merged
MichalStrehovsky merged 2 commits intodotnet:mainfrom
MichalStrehovsky:complement124283
Feb 18, 2026
Merged

Avoid resolving direct awaits to thunks (IL scanner part)#124536
MichalStrehovsky merged 2 commits intodotnet:mainfrom
MichalStrehovsky:complement124283

Conversation

@MichalStrehovsky
Copy link
Member

Reacts to #124283. Fixes native AOT outerloops with runtime async turned on.

Cc @dotnet/ilc-contrib @jakobbotsch

Reacts to dotnet#124283. Fixes native AOT outerloops with runtime async turned on.
Copilot AI review requested due to automatic review settings February 18, 2026 03:49
@dotnet-policy-service
Copy link
Contributor

Tagging subscribers to this area: @agocke, @dotnet/ilc-contrib
See info in area-owners.md if you want to be subscribed.

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 implements the IL scanner part of the optimization from PR #124283 to avoid resolving direct awaits to thunks in NativeAOT. When a direct call to a Task-returning method is encountered during async scanning, the compiler now avoids creating unnecessary async variant thunks, which reduces JIT overhead and enables better optimization.

Changes:

  • Added test for static virtual interface methods with async to validate the fix
  • Refactored IsCallEffectivelyDirect into a reusable extension method
  • Enhanced IL scanner to skip async variant resolution for direct calls that would result in thunks

Reviewed changes

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

Show a summary per file
File Description
src/tests/async/staticvirtual/staticvirtual.csproj New test project using IL SDK with C# compilation
src/tests/async/staticvirtual/staticvirtual.cs Test case for static virtual interface methods with async, validates the thunk avoidance optimization
src/coreclr/tools/Common/Compiler/MethodExtensions.cs Added IsCallEffectivelyDirect extension method for reuse across codebase
src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs Removed IsCallEffectivelyDirect private method and updated to use extension method
src/coreclr/tools/aot/ILCompiler.RyuJit/JitInterface/CorInfoImpl.RyuJit.cs Updated call site to use extension method
src/coreclr/tools/aot/ILCompiler.Compiler/IL/ILImporter.Scanner.cs Added logic to avoid resolving direct calls to async thunks, refactored async variant checks, and updated to use extension method

Copy link
Member

@jakobbotsch jakobbotsch left a comment

Choose a reason for hiding this comment

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

I don't quite understand why it is necessary for correctness. If we think we use the thunk then won't that be an overestimate of what is needed since the thunk also references the real function? This same kind of misreporting should happen whenever we inline something.

Similar JIT optimizations will happen in the future (what I was commenting about in #121622 (comment))

@MichalStrehovsky
Copy link
Member Author

The problem here is that there are two virtual slots for each async method (the thunk and the real body each get a separate slot). IL scanner was predicting a different slot would be needed than what RyuJIT then asked for. We treeshake unused virtual slots based on whole program analysis.

@jakobbotsch
Copy link
Member

Even though these cases being optimized are not virtual?

I expect in the future we will have JIT optimizations that will switch between thunk and the non-thunk versions in ways that will be impossible to predict by the IL scanner. Do you have any thoughts about how we can handle this?

@MichalStrehovsky
Copy link
Member Author

Even though these cases being optimized are not virtual?

These were virtuals (see the added test case). Static virtuals, but we still need to track them like this if it's in shared code. Scanner predicted we're calling the async-call variant of the interface method, but RyuJIT needed the non-async one.

I expect in the future we will have JIT optimizations that will switch between thunk and the non-thunk versions in ways that will be impossible to predict by the IL scanner. Do you have any thoughts about how we can handle this?

We can always go conservative in the scanner. At that point, we'll hopefully be able to measure real-world impact of being conservative and take it from there. Since right now we can't measure real world impacts and we can still model this precisely, we're doing precise.

@jakobbotsch
Copy link
Member

These were virtuals (see the added test case). Static virtuals, but we still need to track them like this if it's in shared code. Scanner predicted we're calling the async-call variant of the interface method, but RyuJIT needed the non-async one.

Does it mean the logic I added has a bug? If the call is virtually dispatched then we should make sure we switch to the async variant so that we don't pessimize the actual runtime async overrides.

@MichalStrehovsky
Copy link
Member Author

Does it mean the logic I added has a bug? If the call is virtually dispatched then we should make sure we switch to the async variant so that we don't pessimize the actual runtime async overrides.

Maybe it was the other way around. I'm not sure how it was anymore; this was a constrained call case. Either way, the scanner will now match JitInterface and the compiler crash is gone.

@MichalStrehovsky MichalStrehovsky merged commit 6051699 into dotnet:main Feb 18, 2026
115 of 118 checks passed
@MichalStrehovsky MichalStrehovsky deleted the complement124283 branch February 18, 2026 11:20
@jakobbotsch
Copy link
Member

I do think there is a bug in the logic. In your test case I see a task-returning thunk created for DoTask on coreclr. We should not be going through a task-returning thunk for your example case.

I suppose we will need to pass additional flag for whether the call/callvirt is constrained to resolveToken for it to properly make the determination.

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

Comments