Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion src/coreclr/inc/corinfo.h
Original file line number Diff line number Diff line change
Expand Up @@ -1487,7 +1487,6 @@ enum CorInfoTokenKind

// token comes from runtime async awaiting pattern
CORINFO_TOKENKIND_Await = 0x2000 | CORINFO_TOKENKIND_Method,
CORINFO_TOKENKIND_AwaitVirtual = 0x4000 | CORINFO_TOKENKIND_Method,
};

struct CORINFO_RESOLVED_TOKEN
Expand Down Expand Up @@ -2290,6 +2289,12 @@ class ICorStaticInfo
CORINFO_CLASS_HANDLE* classArg
) = 0;

// Get the other variant of an async method, if possible.
virtual CORINFO_METHOD_HANDLE getAsyncOtherVariant(
CORINFO_METHOD_HANDLE ftn,
bool* variantIsThunk
) = 0;

// Given T, return the type of the default Comparer<T>.
// Returns null if the type can't be determined exactly.
virtual CORINFO_CLASS_HANDLE getDefaultComparerClass(
Expand Down
4 changes: 4 additions & 0 deletions src/coreclr/inc/icorjitinfoimpl_generated.h
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,10 @@ CORINFO_METHOD_HANDLE getInstantiatedEntry(
CORINFO_METHOD_HANDLE* methodArg,
CORINFO_CLASS_HANDLE* classArg) override;

CORINFO_METHOD_HANDLE getAsyncOtherVariant(
CORINFO_METHOD_HANDLE ftn,
bool* variantIsThunk) override;

CORINFO_CLASS_HANDLE getDefaultComparerClass(
CORINFO_CLASS_HANDLE elemType) override;

Expand Down
10 changes: 5 additions & 5 deletions src/coreclr/inc/jiteeversionguid.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,11 @@

#include <minipal/guid.h>

constexpr GUID JITEEVersionIdentifier = { /* 4379bd40-8b3b-4643-a6da-cb62e55e2e6c */
0x4379bd40,
0x8b3b,
0x4643,
{0xa6, 0xda, 0xcb, 0x62, 0xe5, 0x5e, 0x2e, 0x6c}
constexpr GUID JITEEVersionIdentifier = { /* 22511e72-5ac8-4fc8-83ef-0b61688c68bb */
0x22511e72,
0x5ac8,
0x4fc8,
{0x83, 0xef, 0x0b, 0x61, 0x68, 0x8c, 0x68, 0xbb}
};

#endif // JIT_EE_VERSIONING_GUID_H
4 changes: 1 addition & 3 deletions src/coreclr/interpreter/compiler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6741,9 +6741,7 @@ int InterpCompiler::ApplyLdftnDelegateCtorPeep(const uint8_t* ip, OpcodePeepElem

bool InterpCompiler::ResolveAsyncCallToken(const uint8_t* ip)
{
CorInfoTokenKind tokenKind =
ip[0] == CEE_CALL ? CORINFO_TOKENKIND_Await : CORINFO_TOKENKIND_AwaitVirtual;
ResolveToken(getU4LittleEndian(ip + 1), tokenKind, &m_resolvedAsyncCallToken);
ResolveToken(getU4LittleEndian(ip + 1), CORINFO_TOKENKIND_Await, &m_resolvedAsyncCallToken);
Copy link

Copilot AI Feb 24, 2026

Choose a reason for hiding this comment

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

With CORINFO_TOKENKIND_AwaitVirtual removed, ResolveAsyncCallToken now always resolves using CORINFO_TOKENKIND_Await (even for direct CEE_CALL). Previously the VM could avoid returning an async-thunk variant for direct calls; now the interpreter may end up calling thunks unnecessarily. Consider applying the same direct-call check as the JIT importer (after getCallInfo: if callInfo.kind==CORINFO_CALL and the async variant is a thunk, fall back to resolving the regular method token).

Suggested change
ResolveToken(getU4LittleEndian(ip + 1), CORINFO_TOKENKIND_Await, &m_resolvedAsyncCallToken);
const uint8_t opcode = *ip;
const unsigned token = getU4LittleEndian(ip + 1);
if (opcode == CEE_CALL)
{
ResolveToken(token, CORINFO_TOKENKIND_Method, &m_resolvedAsyncCallToken);
}
else
{
ResolveToken(token, CORINFO_TOKENKIND_Await, &m_resolvedAsyncCallToken);
}

Copilot uses AI. Check for mistakes.
return m_resolvedAsyncCallToken.hMethod != NULL;
}

Expand Down
1 change: 1 addition & 0 deletions src/coreclr/jit/ICorJitInfo_names_generated.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ DEF_CLR_API(getMethodVTableOffset)
DEF_CLR_API(resolveVirtualMethod)
DEF_CLR_API(getUnboxedEntry)
DEF_CLR_API(getInstantiatedEntry)
DEF_CLR_API(getAsyncOtherVariant)
DEF_CLR_API(getDefaultComparerClass)
DEF_CLR_API(getDefaultEqualityComparerClass)
DEF_CLR_API(getSZArrayHelperEnumeratorClass)
Expand Down
10 changes: 10 additions & 0 deletions src/coreclr/jit/ICorJitInfo_wrapper_generated.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,16 @@ CORINFO_METHOD_HANDLE WrapICorJitInfo::getInstantiatedEntry(
return temp;
}

CORINFO_METHOD_HANDLE WrapICorJitInfo::getAsyncOtherVariant(
CORINFO_METHOD_HANDLE ftn,
bool* variantIsThunk)
{
API_ENTER(getAsyncOtherVariant);
CORINFO_METHOD_HANDLE temp = wrapHnd->getAsyncOtherVariant(ftn, variantIsThunk);
API_LEAVE(getAsyncOtherVariant);
return temp;
}

CORINFO_CLASS_HANDLE WrapICorJitInfo::getDefaultComparerClass(
CORINFO_CLASS_HANDLE elemType)
{
Expand Down
9 changes: 7 additions & 2 deletions src/coreclr/jit/ee_il_dll.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -339,7 +339,12 @@ inline var_types Compiler::TypeHandleToVarType(CorInfoType jitType, CORINFO_CLAS
return type;
}

inline CORINFO_CALLINFO_FLAGS combine(CORINFO_CALLINFO_FLAGS flag1, CORINFO_CALLINFO_FLAGS flag2)
constexpr CORINFO_CALLINFO_FLAGS operator|(CORINFO_CALLINFO_FLAGS a, CORINFO_CALLINFO_FLAGS b)
{
return (CORINFO_CALLINFO_FLAGS)(flag1 | flag2);
return (CORINFO_CALLINFO_FLAGS)((uint32_t)a | (uint32_t)b);
}

inline CORINFO_CALLINFO_FLAGS& operator|=(CORINFO_CALLINFO_FLAGS& a, CORINFO_CALLINFO_FLAGS b)
{
return a = (CORINFO_CALLINFO_FLAGS)((uint32_t)a | (uint32_t)b);
}
60 changes: 42 additions & 18 deletions src/coreclr/jit/importer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8556,7 +8556,7 @@ void Compiler::impImportBlockCode(BasicBlock* block)
JITDUMP(" %08X", resolvedToken.token);

eeGetCallInfo(&resolvedToken, (prefixFlags & PREFIX_CONSTRAINED) ? &constrainedResolvedToken : nullptr,
combine(CORINFO_CALLINFO_SECURITYCHECKS, CORINFO_CALLINFO_LDFTN), &callInfo);
CORINFO_CALLINFO_SECURITYCHECKS | CORINFO_CALLINFO_LDFTN, &callInfo);

// This check really only applies to intrinsic Array.Address methods
if (callInfo.sig.callConv & CORINFO_CALLCONV_PARAMTYPE)
Expand Down Expand Up @@ -8595,8 +8595,7 @@ void Compiler::impImportBlockCode(BasicBlock* block)
JITDUMP(" %08X", resolvedToken.token);

eeGetCallInfo(&resolvedToken, nullptr /* constraint typeRef */,
combine(combine(CORINFO_CALLINFO_SECURITYCHECKS, CORINFO_CALLINFO_LDFTN),
CORINFO_CALLINFO_CALLVIRT),
CORINFO_CALLINFO_SECURITYCHECKS | CORINFO_CALLINFO_LDFTN | CORINFO_CALLINFO_CALLVIRT,
&callInfo);

// This check really only applies to intrinsic Array.Address methods
Expand Down Expand Up @@ -8729,7 +8728,7 @@ void Compiler::impImportBlockCode(BasicBlock* block)
_impResolveToken(CORINFO_TOKENKIND_NewObj);

eeGetCallInfo(&resolvedToken, nullptr /* constraint typeRef*/,
combine(CORINFO_CALLINFO_SECURITYCHECKS, CORINFO_CALLINFO_ALLOWINSTPARAM), &callInfo);
CORINFO_CALLINFO_SECURITYCHECKS | CORINFO_CALLINFO_ALLOWINSTPARAM, &callInfo);

mflags = callInfo.methodFlags;

Expand Down Expand Up @@ -8982,16 +8981,8 @@ void Compiler::impImportBlockCode(BasicBlock* block)

if (isAwait)
{
_impResolveToken(opcode == CEE_CALLVIRT ? CORINFO_TOKENKIND_AwaitVirtual
: CORINFO_TOKENKIND_Await);
if (resolvedToken.hMethod != nullptr)
{
// There is a runtime async variant that is implicitly awaitable, just call that.
// skip the await pattern to the last token.
codeAddr = codeAddrAfterMatch;
opcodeOffs = awaitOffset;
}
else
_impResolveToken(CORINFO_TOKENKIND_Await);
if (resolvedToken.hMethod == nullptr)
{
// This can happen in cases when the Task-returning method is not a runtime Async
// function. For example "T M1<T>(T arg) => arg" when called with a Task argument.
Expand All @@ -9009,12 +9000,45 @@ void Compiler::impImportBlockCode(BasicBlock* block)
_impResolveToken(CORINFO_TOKENKIND_Method);
}

CORINFO_CALLINFO_FLAGS flags = CORINFO_CALLINFO_ALLOWINSTPARAM | CORINFO_CALLINFO_SECURITYCHECKS;
if (opcode == CEE_CALLVIRT)
{
flags |= CORINFO_CALLINFO_CALLVIRT;
}

eeGetCallInfo(&resolvedToken,
(prefixFlags & PREFIX_CONSTRAINED) ? &constrainedResolvedToken : nullptr,
// this is how impImportCall invokes getCallInfo
combine(combine(CORINFO_CALLINFO_ALLOWINSTPARAM, CORINFO_CALLINFO_SECURITYCHECKS),
(opcode == CEE_CALLVIRT) ? CORINFO_CALLINFO_CALLVIRT : CORINFO_CALLINFO_NONE),
(prefixFlags & PREFIX_CONSTRAINED) ? &constrainedResolvedToken : nullptr, flags,
&callInfo);

if (isAwait && (callInfo.kind == CORINFO_CALL))
{
assert(callInfo.sig.isAsyncCall());
bool isSyncCallThunk;
info.compCompHnd->getAsyncOtherVariant(callInfo.hMethod, &isSyncCallThunk);
Copy link

Copilot AI Feb 24, 2026

Choose a reason for hiding this comment

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

The return value of getAsyncOtherVariant is not being captured or used. The code re-resolves the token with CORINFO_TOKENKIND_Method instead of using the returned method handle directly. While this may work correctly in most cases, there could be edge cases where re-resolving doesn't give the exact same specialized method descriptor, particularly in generic contexts. Consider capturing and using the return value directly to avoid the redundant token resolution and ensure correctness.

Suggested change
info.compCompHnd->getAsyncOtherVariant(callInfo.hMethod, &isSyncCallThunk);
CORINFO_METHOD_HANDLE asyncOtherVariant =
info.compCompHnd->getAsyncOtherVariant(callInfo.hMethod, &isSyncCallThunk);
if ((asyncOtherVariant != nullptr) && !isSyncCallThunk)
{
callInfo.hMethod = asyncOtherVariant;
}

Copilot uses AI. Check for mistakes.
if (!isSyncCallThunk)
Comment on lines +9016 to +9018
Copy link

Copilot AI Feb 24, 2026

Choose a reason for hiding this comment

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

The variable name isSyncCallThunk is misleading. When getAsyncOtherVariant is called on an async method, it returns the sync (task-returning) variant. The bool parameter indicates whether that returned sync variant is a thunk. Consider renaming to otherVariantIsThunk or syncVariantIsThunk to make the meaning clearer.

Suggested change
bool isSyncCallThunk;
info.compCompHnd->getAsyncOtherVariant(callInfo.hMethod, &isSyncCallThunk);
if (!isSyncCallThunk)
bool syncVariantIsThunk;
info.compCompHnd->getAsyncOtherVariant(callInfo.hMethod, &syncVariantIsThunk);
if (!syncVariantIsThunk)

Copilot uses AI. Check for mistakes.
{
// Otherwise the async variant that we got is a thunk.
// Switch back to the non-async task-returning call. There is no reason to go through the
// thunk.
_impResolveToken(CORINFO_TOKENKIND_Method);
prefixFlags &= ~(PREFIX_IS_TASK_AWAIT | PREFIX_TASK_AWAIT_CONTINUE_ON_CAPTURED_CONTEXT);
isAwait = false;

JITDUMP(
"Async variant provided by VM is a thunk, switching direct call to synchronous task-returning method\n");
eeGetCallInfo(&resolvedToken,
(prefixFlags & PREFIX_CONSTRAINED) ? &constrainedResolvedToken : nullptr,
flags, &callInfo);
}
}

if (isAwait)
{
// If the synchronous call is a thunk then it means the async variant is not a thunk and we
// prefer to directly call it. Skip the await pattern to the last token.
codeAddr = codeAddrAfterMatch;
opcodeOffs = awaitOffset;
}
}
else
{
Expand Down
39 changes: 23 additions & 16 deletions src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1558,6 +1558,27 @@ static CORINFO_RESOLVED_TOKEN CreateResolvedTokenFromMethod(CorInfoImpl jitInter
return null;
}

private CORINFO_METHOD_STRUCT_* getAsyncOtherVariant(CORINFO_METHOD_STRUCT_* ftn, ref bool variantIsThunk)
{
MethodDesc method = HandleToObject(ftn);
if (method.IsAsyncVariant())
Comment on lines +1561 to +1564
Copy link

Copilot AI Feb 24, 2026

Choose a reason for hiding this comment

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

getAsyncOtherVariant is wired into the JIT callback table, and the JIT importer now calls it for await-pattern direct calls. Throwing NotImplementedException here will break NativeAOT/ReadyToRun compilation of async methods; implement this by mapping between the async variant and task-returning variant (and set variantIsThunk based on IsAsyncThunk()/IsAsync on the returned variant).

Copilot uses AI. Check for mistakes.
{
method = method.GetTargetOfAsyncVariant();
}
else if (method.Signature.ReturnsTaskOrValueTask())
{
method = method.GetAsyncVariant();
}
else
{
variantIsThunk = false;
return null;
}

variantIsThunk = method?.IsAsyncThunk() ?? false;
return ObjectToHandle(method);
}

private CORINFO_CLASS_STRUCT_* getDefaultComparerClass(CORINFO_CLASS_STRUCT_* elemType)
{
TypeDesc comparand = HandleToObject(elemType);
Expand Down Expand Up @@ -1778,7 +1799,7 @@ private object GetRuntimeDeterminedObjectForToken(ref CORINFO_RESOLVED_TOKEN pRe
if (pResolvedToken.tokenType == CorInfoTokenKind.CORINFO_TOKENKIND_Newarr)
result = ((TypeDesc)result).MakeArrayType();

if (pResolvedToken.tokenType is CorInfoTokenKind.CORINFO_TOKENKIND_Await or CorInfoTokenKind.CORINFO_TOKENKIND_AwaitVirtual)
if (pResolvedToken.tokenType is CorInfoTokenKind.CORINFO_TOKENKIND_Await)
result = _compilation.TypeSystemContext.GetAsyncVariantMethod((MethodDesc)result);

return result;
Expand Down Expand Up @@ -1868,7 +1889,7 @@ private void resolveToken(ref CORINFO_RESOLVED_TOKEN pResolvedToken)
_compilation.NodeFactory.MetadataManager.GetDependenciesDueToAccess(ref _additionalDependencies, _compilation.NodeFactory, (MethodIL)methodIL, method);
#endif

if (pResolvedToken.tokenType is CorInfoTokenKind.CORINFO_TOKENKIND_Await or CorInfoTokenKind.CORINFO_TOKENKIND_AwaitVirtual)
if (pResolvedToken.tokenType is CorInfoTokenKind.CORINFO_TOKENKIND_Await)
{
// in rare cases a method that returns Task is not actually TaskReturning (i.e. returns T).
// we cannot resolve to an Async variant in such case.
Expand All @@ -1878,20 +1899,6 @@ private void resolveToken(ref CORINFO_RESOLVED_TOKEN pResolvedToken)
// Don't get async variant of Delegate.Invoke method; the pointed to method is not an async variant either.
allowAsyncVariant = allowAsyncVariant && !method.OwningType.IsDelegate;

#if !READYTORUN
if (allowAsyncVariant)
{
bool isDirect = pResolvedToken.tokenType == CorInfoTokenKind.CORINFO_TOKENKIND_Await || method.IsCallEffectivelyDirect();
if (isDirect && !method.IsAsync)
{
// Async variant would be a thunk. Do not resolve direct calls
// to async thunks. That just creates and JITs unnecessary
// thunks, and the thunks are harder for the JIT to optimize.
allowAsyncVariant = false;
}
}
#endif

method = allowAsyncVariant
? _compilation.TypeSystemContext.GetAsyncVariantMethod(method)
: null;
Expand Down
17 changes: 17 additions & 0 deletions src/coreclr/tools/Common/JitInterface/CorInfoImpl_generated.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ static ICorJitInfoCallbacks()
s_callbacks.resolveVirtualMethod = &_resolveVirtualMethod;
s_callbacks.getUnboxedEntry = &_getUnboxedEntry;
s_callbacks.getInstantiatedEntry = &_getInstantiatedEntry;
s_callbacks.getAsyncOtherVariant = &_getAsyncOtherVariant;
s_callbacks.getDefaultComparerClass = &_getDefaultComparerClass;
s_callbacks.getDefaultEqualityComparerClass = &_getDefaultEqualityComparerClass;
s_callbacks.getSZArrayHelperEnumeratorClass = &_getSZArrayHelperEnumeratorClass;
Expand Down Expand Up @@ -220,6 +221,7 @@ static ICorJitInfoCallbacks()
public delegate* unmanaged<IntPtr, IntPtr*, CORINFO_DEVIRTUALIZATION_INFO*, byte> resolveVirtualMethod;
public delegate* unmanaged<IntPtr, IntPtr*, CORINFO_METHOD_STRUCT_*, bool*, CORINFO_METHOD_STRUCT_*> getUnboxedEntry;
public delegate* unmanaged<IntPtr, IntPtr*, CORINFO_METHOD_STRUCT_*, CORINFO_METHOD_STRUCT_**, CORINFO_CLASS_STRUCT_**, CORINFO_METHOD_STRUCT_*> getInstantiatedEntry;
public delegate* unmanaged<IntPtr, IntPtr*, CORINFO_METHOD_STRUCT_*, bool*, CORINFO_METHOD_STRUCT_*> getAsyncOtherVariant;
public delegate* unmanaged<IntPtr, IntPtr*, CORINFO_CLASS_STRUCT_*, CORINFO_CLASS_STRUCT_*> getDefaultComparerClass;
public delegate* unmanaged<IntPtr, IntPtr*, CORINFO_CLASS_STRUCT_*, CORINFO_CLASS_STRUCT_*> getDefaultEqualityComparerClass;
public delegate* unmanaged<IntPtr, IntPtr*, CORINFO_CLASS_STRUCT_*, CORINFO_CLASS_STRUCT_*> getSZArrayHelperEnumeratorClass;
Expand Down Expand Up @@ -665,6 +667,21 @@ private static byte _resolveVirtualMethod(IntPtr thisHandle, IntPtr* ppException
}
}

[UnmanagedCallersOnly]
private static CORINFO_METHOD_STRUCT_* _getAsyncOtherVariant(IntPtr thisHandle, IntPtr* ppException, CORINFO_METHOD_STRUCT_* ftn, bool* variantIsThunk)
{
var _this = GetThis(thisHandle);
try
{
return _this.getAsyncOtherVariant(ftn, ref *variantIsThunk);
Copy link

Copilot AI Feb 24, 2026

Choose a reason for hiding this comment

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

The unmanaged entrypoint for getAsyncOtherVariant dereferences variantIsThunk (ref *variantIsThunk) without a null check. The native signature allows callers to pass nullptr (and some shims already treat it as optional), so this can AV; handle a null pointer by using a local temporary bool and only writing back when non-null.

Suggested change
return _this.getAsyncOtherVariant(ftn, ref *variantIsThunk);
bool localVariantIsThunk = variantIsThunk is not null && *variantIsThunk;
CORINFO_METHOD_STRUCT_* result = _this.getAsyncOtherVariant(ftn, ref localVariantIsThunk);
if (variantIsThunk is not null)
{
*variantIsThunk = localVariantIsThunk;
}
return result;

Copilot uses AI. Check for mistakes.
}
catch (Exception ex)
{
*ppException = _this.AllocException(ex);
return default;
}
}

[UnmanagedCallersOnly]
private static CORINFO_CLASS_STRUCT_* _getDefaultComparerClass(IntPtr thisHandle, IntPtr* ppException, CORINFO_CLASS_STRUCT_* elemType)
{
Expand Down
1 change: 0 additions & 1 deletion src/coreclr/tools/Common/JitInterface/CorInfoTypes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1476,7 +1476,6 @@ public enum CorInfoTokenKind

// token comes from runtime async awaiting pattern
CORINFO_TOKENKIND_Await = 0x2000 | CORINFO_TOKENKIND_Method,
CORINFO_TOKENKIND_AwaitVirtual = 0x4000 | CORINFO_TOKENKIND_Method,
};

// These are error codes returned by CompileMethod
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,7 @@ FUNCTIONS
bool resolveVirtualMethod(CORINFO_DEVIRTUALIZATION_INFO* info);
CORINFO_METHOD_HANDLE getUnboxedEntry(CORINFO_METHOD_HANDLE ftn, bool* requiresInstMethodTableArg);
CORINFO_METHOD_HANDLE getInstantiatedEntry(CORINFO_METHOD_HANDLE ftn, CORINFO_METHOD_HANDLE* methodArg, CORINFO_CLASS_HANDLE* classArg);
CORINFO_METHOD_HANDLE getAsyncOtherVariant(CORINFO_METHOD_HANDLE ftn, bool* variantIsThunk);
CORINFO_CLASS_HANDLE getDefaultComparerClass(CORINFO_CLASS_HANDLE elemType);
CORINFO_CLASS_HANDLE getDefaultEqualityComparerClass(CORINFO_CLASS_HANDLE elemType);
CORINFO_CLASS_HANDLE getSZArrayHelperEnumeratorClass(CORINFO_CLASS_HANDLE elemType);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -484,18 +484,6 @@ private void ImportCall(ILOpcode opcode, int token)
// Don't get async variant of Delegate.Invoke method; the pointed to method is not an async variant either.
allowAsyncVariant = allowAsyncVariant && !method.OwningType.IsDelegate;

if (allowAsyncVariant)
{
bool isDirect = opcode == ILOpcode.call || method.IsCallEffectivelyDirect();
if (isDirect && !method.IsAsync)
{
// Async variant would be a thunk. Do not resolve direct calls
// to async thunks. That just creates and JITs unnecessary
// thunks, and the thunks are harder for the JIT to optimize.
allowAsyncVariant = false;
}
}

if (allowAsyncVariant && MatchTaskAwaitPattern())
{
runtimeDeterminedMethod = _factory.TypeSystemContext.GetAsyncVariantMethod(runtimeDeterminedMethod);
Expand Down
3 changes: 3 additions & 0 deletions src/coreclr/tools/aot/crossgen2.slnx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@
<Platform Name="x64" />
<Platform Name="x86" />
</Configurations>
<Project Path="../../../tools/illink/src/ILLink.CodeFix/ILLink.CodeFixProvider.csproj">
<BuildType Solution="Checked|*" Project="Debug" />
</Project>
Comment on lines +10 to +12
Copy link

Copilot AI Feb 24, 2026

Choose a reason for hiding this comment

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

This adds ILLink.CodeFixProvider to the crossgen2 solution. This appears unrelated to the PR's described JIT/EE async-variant work; please confirm it's intentional (and if not, revert it to keep the PR focused).

Copilot uses AI. Check for mistakes.
Comment on lines +10 to +12
Copy link

Copilot AI Feb 24, 2026

Choose a reason for hiding this comment

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

This change adds ILLink.CodeFix project references to the solution files. This appears unrelated to the async-to-sync call optimization that is the focus of this PR. If this is an intentional change, it should be mentioned in the PR description or done in a separate commit/PR for clarity.

Copilot uses AI. Check for mistakes.
<Project Path="../../../tools/illink/src/ILLink.RoslynAnalyzer/ILLink.RoslynAnalyzer.csproj">
<BuildType Solution="Checked|*" Project="Debug" />
</Project>
Expand Down
3 changes: 3 additions & 0 deletions src/coreclr/tools/aot/ilc.slnx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@
<Platform Name="x64" />
<Platform Name="x86" />
</Configurations>
<Project Path="../../../tools/illink/src/ILLink.CodeFix/ILLink.CodeFixProvider.csproj">
<BuildType Solution="Checked|*" Project="Debug" />
</Project>
Comment on lines +10 to +12
Copy link

Copilot AI Feb 24, 2026

Choose a reason for hiding this comment

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

This adds ILLink.CodeFixProvider to the ilc solution. This seems unrelated to the PR's stated JIT/EE async-variant refactor and may affect solution build time/dependencies; please confirm this is intentional and, if not, remove it from the PR.

Copilot uses AI. Check for mistakes.
<Project Path="../../../tools/illink/src/ILLink.RoslynAnalyzer/ILLink.RoslynAnalyzer.csproj">
<BuildType Solution="Checked|*" Project="Debug" />
</Project>
Expand Down
11 changes: 11 additions & 0 deletions src/coreclr/tools/aot/jitinterface/jitinterface_generated.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ struct JitInterfaceCallbacks
bool (* resolveVirtualMethod)(void * thisHandle, CorInfoExceptionClass** ppException, CORINFO_DEVIRTUALIZATION_INFO* info);
CORINFO_METHOD_HANDLE (* getUnboxedEntry)(void * thisHandle, CorInfoExceptionClass** ppException, CORINFO_METHOD_HANDLE ftn, bool* requiresInstMethodTableArg);
CORINFO_METHOD_HANDLE (* getInstantiatedEntry)(void * thisHandle, CorInfoExceptionClass** ppException, CORINFO_METHOD_HANDLE ftn, CORINFO_METHOD_HANDLE* methodArg, CORINFO_CLASS_HANDLE* classArg);
CORINFO_METHOD_HANDLE (* getAsyncOtherVariant)(void * thisHandle, CorInfoExceptionClass** ppException, CORINFO_METHOD_HANDLE ftn, bool* variantIsThunk);
CORINFO_CLASS_HANDLE (* getDefaultComparerClass)(void * thisHandle, CorInfoExceptionClass** ppException, CORINFO_CLASS_HANDLE elemType);
CORINFO_CLASS_HANDLE (* getDefaultEqualityComparerClass)(void * thisHandle, CorInfoExceptionClass** ppException, CORINFO_CLASS_HANDLE elemType);
CORINFO_CLASS_HANDLE (* getSZArrayHelperEnumeratorClass)(void * thisHandle, CorInfoExceptionClass** ppException, CORINFO_CLASS_HANDLE elemType);
Expand Down Expand Up @@ -395,6 +396,16 @@ class JitInterfaceWrapper : public ICorJitInfo
return temp;
}

virtual CORINFO_METHOD_HANDLE getAsyncOtherVariant(
CORINFO_METHOD_HANDLE ftn,
bool* variantIsThunk)
{
CorInfoExceptionClass* pException = nullptr;
CORINFO_METHOD_HANDLE temp = _callbacks->getAsyncOtherVariant(_thisHandle, &pException, ftn, variantIsThunk);
if (pException != nullptr) throw pException;
return temp;
}

virtual CORINFO_CLASS_HANDLE getDefaultComparerClass(
CORINFO_CLASS_HANDLE elemType)
{
Expand Down
Loading
Loading