Skip to content

Conversation

@MichalStrehovsky
Copy link
Member

Signature.IsAsyncCall is not true for these because we currently only set it on variants.

We could also fix this when we materialize the signature from the ECMA file, but I'm not sure we want to go there.

Cc @dotnet/ilc-contrib

`Signature.IsAsyncCall` is not true for these.

We could also fix this when we materialize the signature from the ECMA file, but I'm not sure we want to go there.
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 enhances async method handling in the JIT interface by ensuring that special async intrinsics (async methods that don't return Task or ValueTask) are correctly marked with the async calling convention, even though their signatures don't have the async bit set.

  • Adds logic to set CORINFO_CALLCONV_ASYNCCALL for async intrinsics in signature info
  • Adds logic to set CORJIT_FLAG_ASYNC for async intrinsics in JIT flags
  • Compensates for special CoreLib async intrinsics that have IsAsync metadata but don't return Task/ValueTask

@jkotas
Copy link
Member

jkotas commented Nov 6, 2025

We could also fix this when we materialize the signature from the ECMA file, but I'm not sure we want to go there.

Do we actually need MethodSignatureFlags.AsyncCall and the signature munging then? It looks like that all places that use it will need to special case the async intrinsics too.

The async calling convention is similar to the hidden argument in shared generic calling convention. We do not keep track of the hidden parameter in MethodSignatureFlags. We check for it right when the JIT asks for the low-level calling convention details. I think the async calling convention may want to be on the same plan.

@jkotas
Copy link
Member

jkotas commented Nov 6, 2025

static bool IsAsyncCall(this MethodDesc method) => method.IsAsync && (method.IsAsyncVariant() || !method.ReturnsTaskOrValueTask());

@VSadov
Copy link
Member

VSadov commented Nov 6, 2025

Do we actually need MethodSignatureFlags.AsyncCall and the signature munging then? It looks like that all places that use it will need to special case the async intrinsics too.

There are methods that are AsyncExplicitImpl and also not intrinsics. Like UnsafeAwaitAwaiter.

We reserve the right to have Async callconv methods that are not task-returning in the corelib. The need sometimes arises in the infrastructure code. We had more of such helpers originally, now we have less, but as we go into optimizing the managed infrastructure or its interfacing with Task/ValueTask, we may have more. As an example: TransparentAwait was a recent addition - to improve support of ValueTaskSource-based ValueTask in terms of efficiency and correctness.

The reasons why only in corelib is just because we do not want to make such methods a public-facing feature. The behavior is well defined and can be documented, and maybe even have some special support in Roslyn. As a matter of scoping public API we chose not to.

I am just pointing that, while methods like Await are intrinsics, that is just because JIT needs to pattern-match them in optimizations. It is mostly a coincidence that many AsyncExplicitImpl are also intrinsics.

@jkotas
Copy link
Member

jkotas commented Nov 6, 2025

while methods like Await are intrinsics, that is just because JIT needs to pattern-match them in optimizations. It is mostly a coincidence that many AsyncExplicitImpl are also intrinsics.

This matches how [Intrinsic] is used in general. [Intrinsic] means "This method is special-cased by the runtime in some way.". In many case, it does not mean that the method implementation is provided by the JIT.

@VSadov
Copy link
Member

VSadov commented Nov 6, 2025

while methods like Await are intrinsics, that is just because JIT needs to pattern-match them in optimizations. It is mostly a coincidence that many AsyncExplicitImpl are also intrinsics.

This matches how [Intrinsic] is used in general. [Intrinsic] means "This method is special-cased by the runtime in some way.". In many case, it does not mean that the method implementation is provided by the JIT.

In some sense everything is special-cased - based on a signature, on presence of generic parameters, on MethodImpl, ...

These are not special-cased. They are just async methods. MethodImpl.Async says so.

There is no requirement for the set of AsyncExplicitImpl to be known to the JIT by name and there is no requirement for them to be marked as [Intrinsic]. Early in the prototype we had such requirement and some methods were "known by their name to be async", it was causing problems as we effectively ended up with more than one way of specifying async-ness of a method.

Also adding/removing/renaming such methods was more laborious as JIT code would need to be changed in sync. Possibly would need to change R2R version every time, if we supported R2R - that is just for some internal implementation refactoring.

@MichalStrehovsky
Copy link
Member Author

@copilot replace uses of MethodSignature.IsAsyncCall with a new extension method static bool IsAsyncCall(this MethodDesc method) => method.IsAsync && (method.IsAsyncVariant() || !method.ReturnsTaskOrValueTask());. Place the new extension method next to ReturnsTaskOrValueTask. Then remove MethodSignatureFlags.AsyncCall.

Copy link
Contributor

Copilot AI commented Nov 6, 2025

@MichalStrehovsky I've opened a new pull request, #121420, to work on those changes. Once the pull request is ready, I'll request review from you.

@MichalStrehovsky
Copy link
Member Author

Okay so "Mention copilot in a comment to make changes to this pull request." above is a lie. It's going to make a new pull request.

@jkotas
Copy link
Member

jkotas commented Nov 6, 2025

It's going to make a new pull request.

New pull request against your branch

…on method (#121420)

Consolidates async calling convention detection logic into a single
`MethodDesc` extension method, removing the need for
`MethodSignatureFlags.AsyncCall` flag.

**Changes:**

- **Added `IsAsyncCall` extension method** on `MethodDesc` that unifies
detection logic: returns true for async variants and async intrinsics
that don't return Task/ValueTask
- **Removed `MethodSignatureFlags.AsyncCall`** enum value and
`MethodSignature.IsAsyncCall` property
- **Updated call sites** in `CorInfoImpl.cs` to use the new extension
method for `CORINFO_CALLCONV_ASYNCCALL` and `CORJIT_FLAG_ASYNC`
- **Simplified `AsyncMethodVariant`** to no longer set the AsyncCall
flag in signature

**Rationale:**

Async calling convention is now determined at the MethodDesc level
(similar to hidden parameters in generic calling conventions) rather
than being stored in signature flags. This properly handles both async
variants (which have the flag set by signature transformation) and async
intrinsics (which have `IsAsync` metadata but don't return
Task/ValueTask).

```csharp
// Before: Logic scattered across multiple locations
if (method.IsAsync && !method.Signature.ReturnsTaskOrValueTask())
    sig->callConv |= CorInfoCallConv.CORINFO_CALLCONV_ASYNCCALL;

// After: Unified in extension method
if (method.IsAsyncCall())
    sig->callConv |= CorInfoCallConv.CORINFO_CALLCONV_ASYNCCALL;
```

<!-- START COPILOT CODING AGENT TIPS -->
---

✨ Let Copilot coding agent [set things up for
you](https://github.com/dotnet/runtime/issues/new?title=✨+Set+up+Copilot+instructions&body=Configure%20instructions%20for%20this%20repository%20as%20documented%20in%20%5BBest%20practices%20for%20Copilot%20coding%20agent%20in%20your%20repository%5D%28https://gh.io/copilot-coding-agent-tips%29%2E%0A%0A%3COnboard%20this%20repo%3E&assignees=copilot)
— coding agent works faster and does higher quality work when set up for
your repo.

---------

Co-authored-by: copilot-swe-agent[bot] <[email protected]>
Co-authored-by: MichalStrehovsky <[email protected]>
Copy link
Member

@jkotas jkotas left a comment

Choose a reason for hiding this comment

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

Thanks

@MichalStrehovsky
Copy link
Member Author

/ba-g timeout on android

@MichalStrehovsky MichalStrehovsky merged commit 663e792 into main Nov 7, 2025
86 of 97 checks passed
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.

6 participants