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
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,13 @@ public static partial class AsyncHelpers
// * a continuation object if the call requires suspension.
// In this case the formal result of the call is undefined.
[Intrinsic]
private static Continuation? AsyncCallContinuation() => throw new UnreachableException();
internal static Continuation? AsyncCallContinuation() => throw new UnreachableException(); // Unconditionally expanded intrinsic

// Set call continuation argument for an upcoming call with runtime async calling convention.
// Can only be used shortly before the call (with no interfering control flow instructions before the call).
// If used anywhere in the method it must be present before _all_ calls with runtime async calling convention.
[Intrinsic]
internal static void SetAsyncCallContinuationArg(Continuation continuation) => throw new UnreachableException();

// Used during suspensions to hold the continuation chain and on what we are waiting.
// Methods like FinalizeTaskReturningThunk will unlink the state and wrap into a Task.
Expand Down
5 changes: 5 additions & 0 deletions src/coreclr/jit/async.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -484,6 +484,11 @@ bool AsyncLiveness::IsLocalCaptureUnnecessary(unsigned lclNum)
return true;
}

if (lclNum == m_comp->lvaNextAsyncCallContArgVar)
{
return true;
}

return false;
}

Expand Down
2 changes: 2 additions & 0 deletions src/coreclr/jit/compiler.h
Original file line number Diff line number Diff line change
Expand Up @@ -3976,6 +3976,8 @@ class Compiler

bool lvaInlineeReturnSpillTempFreshlyCreated = false; // True if the temp was freshly created for the inlinee return

unsigned lvaNextAsyncCallContArgVar = BAD_VAR_NUM;

#if FEATURE_FIXED_OUT_ARGS
unsigned lvaOutgoingArgSpaceVar = BAD_VAR_NUM; // var that represents outgoing argument space
PhasedVar<unsigned> lvaOutgoingArgSpaceSize; // size of fixed outgoing argument space
Expand Down
26 changes: 24 additions & 2 deletions src/coreclr/jit/importercalls.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -875,7 +875,10 @@ var_types Compiler::impImportCall(OPCODE opcode,
// stubs and the VM inserts the arg itself.
if (call->AsCall()->IsAsync() && (opcode != CEE_CALLI))
{
call->AsCall()->gtArgs.PushFront(this, NewCallArg::Primitive(gtNewNull(), TYP_REF)
GenTree* arg = lvaNextAsyncCallContArgVar != BAD_VAR_NUM
? (GenTree*)gtNewLclVarNode(lvaNextAsyncCallContArgVar)
: gtNewNull();
Comment on lines +878 to +880
Copy link

Copilot AI Nov 10, 2025

Choose a reason for hiding this comment

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

The conditional logic to determine the async continuation argument is duplicated (appears twice at lines 878-880 and 897-899). Consider extracting this logic into a helper method like GetAsyncContinuationArg() to avoid duplication and improve maintainability.

Copilot uses AI. Check for mistakes.
call->AsCall()->gtArgs.PushFront(this, NewCallArg::Primitive(arg, TYP_REF)
.WellKnown(WellKnownArg::AsyncContinuation));
}

Expand All @@ -891,7 +894,10 @@ var_types Compiler::impImportCall(OPCODE opcode,
// stubs and the VM inserts the arg itself.
if (call->AsCall()->IsAsync() && (opcode != CEE_CALLI))
{
call->AsCall()->gtArgs.PushBack(this, NewCallArg::Primitive(gtNewNull(), TYP_REF)
GenTree* arg = lvaNextAsyncCallContArgVar != BAD_VAR_NUM
? (GenTree*)gtNewLclVarNode(lvaNextAsyncCallContArgVar)
: gtNewNull();
call->AsCall()->gtArgs.PushBack(this, NewCallArg::Primitive(arg, TYP_REF)
.WellKnown(WellKnownArg::AsyncContinuation));
}

Expand Down Expand Up @@ -3332,6 +3338,18 @@ GenTree* Compiler::impIntrinsic(CORINFO_CLASS_HANDLE clsHnd,
return node;
}

if (ni == NI_System_Runtime_CompilerServices_AsyncHelpers_SetAsyncCallContinuationArg)
{
if (lvaNextAsyncCallContArgVar == BAD_VAR_NUM)
{
lvaNextAsyncCallContArgVar = lvaGrabTemp(false DEBUGARG("Async call continuation arg"));
lvaGetDesc(lvaNextAsyncCallContArgVar)->lvType = TYP_REF;
}

GenTree* node = gtNewStoreLclVarNode(lvaNextAsyncCallContArgVar, impPopStack().val);
return node;
}

if (ni == NI_System_Runtime_CompilerServices_AsyncHelpers_AsyncSuspend)
{
GenTree* node = gtNewOperNode(GT_RETURN_SUSPEND, TYP_VOID, impPopStack().val);
Expand Down Expand Up @@ -10876,6 +10894,10 @@ NamedIntrinsic Compiler::lookupNamedIntrinsic(CORINFO_METHOD_HANDLE method)
{
result = NI_System_Runtime_CompilerServices_AsyncHelpers_AsyncCallContinuation;
}
else if (strcmp(methodName, "SetAsyncCallContinuationArg") == 0)
{
result = NI_System_Runtime_CompilerServices_AsyncHelpers_SetAsyncCallContinuationArg;
}
}
else if (strcmp(className, "StaticsHelpers") == 0)
{
Expand Down
1 change: 1 addition & 0 deletions src/coreclr/jit/namedintrinsiclist.h
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ enum NamedIntrinsic : unsigned short
NI_System_Runtime_CompilerServices_AsyncHelpers_AsyncSuspend,
NI_System_Runtime_CompilerServices_AsyncHelpers_Await,
NI_System_Runtime_CompilerServices_AsyncHelpers_AsyncCallContinuation,
NI_System_Runtime_CompilerServices_AsyncHelpers_SetAsyncCallContinuationArg,

NI_System_Runtime_CompilerServices_StaticsHelpers_VolatileReadAsByref,

Expand Down
1 change: 1 addition & 0 deletions src/coreclr/vm/corelib.h
Original file line number Diff line number Diff line change
Expand Up @@ -750,6 +750,7 @@ DEFINE_METHOD(ASYNC_HELPERS, CAPTURE_CONTINUATION_CONTEXT, CaptureContinuat
DEFINE_METHOD(ASYNC_HELPERS, CAPTURE_CONTEXTS, CaptureContexts, NoSig)
DEFINE_METHOD(ASYNC_HELPERS, RESTORE_CONTEXTS, RestoreContexts, NoSig)
DEFINE_METHOD(ASYNC_HELPERS, ASYNC_CALL_CONTINUATION, AsyncCallContinuation, NoSig)
DEFINE_METHOD(ASYNC_HELPERS, SET_ASYNC_CALL_CONTINUATION_ARG, SetAsyncCallContinuationArg, SM_Obj_RetVoid)

#ifdef TARGET_BROWSER
DEFINE_METHOD(ASYNC_HELPERS, HANDLE_ASYNC_ENTRYPOINT, HandleAsyncEntryPoint, SM_TaskOfInt_RetInt)
Expand Down
94 changes: 37 additions & 57 deletions src/coreclr/vm/jitinterface.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9151,6 +9151,37 @@ void CEEInfo::getFunctionEntryPoint(CORINFO_METHOD_HANDLE ftnHnd,
accessType = IAT_PVALUE;
}
}
#ifdef FEATURE_TIERED_COMPILATION
else if (ftn->IsAsyncMethod() && m_pMethodBeingCompiled->IsILStub()
&& m_pMethodBeingCompiled->AsDynamicMethodDesc()->IsAsyncResumptionStub())
{
// We are looking at a call to an async method from within an async method's
// resumption stub. We need to make sure we call the right version of the
// async method here.

// Resumption stubs are uniquely coupled to the code version (since the
// continuation is), so we need to make sure we always keep calling the
// same version here.
PrepareCodeConfig* config = GetThread()->GetCurrentPrepareCodeConfig();
NativeCodeVersion ncv = config->GetCodeVersion();
if (ncv.GetOptimizationTier() == NativeCodeVersion::OptimizationTier1OSR)
{
#ifdef FEATURE_ON_STACK_REPLACEMENT
// The OSR version needs to resume in the tier0 version. The tier0
// version will handle setting up the frame that the OSR version
// expects and then delegating back into the OSR version (knowing to do
// so through information stored in the continuation).
unsigned osrIlOffset = 0;
PatchpointInfo* patchpointInfo = getOSRInfo(&osrIlOffset);
_ASSERTE(patchpointInfo != NULL);
ret = (void*)(patchpointInfo->GetTier0EntryPoint());
accessType = IAT_VALUE;
#else // !FEATURE_ON_STACK_REPLACEMENT
_ASSERTE(!"Unexpected optimization tier with OSR disabled");
#endif // FEATURE_ON_STACK_REPLACEMENT
}
}
#endif // FEATURE_TIERED_COMPILATION
else if (ftn->IsVersionableWithPrecode() && (ftn->GetPrecodeType() == PRECODE_FIXUP) && !ftn->IsPointingToStableNativeCode())
{
ret = ((FixupPrecode*)ftn->GetOrCreatePrecode())->GetTargetSlot();
Expand Down Expand Up @@ -14659,13 +14690,15 @@ CORINFO_METHOD_HANDLE CEEJitInfo::getAsyncResumptionStub(void** entryPoint)
Signature stubSig = BuildResumptionStubSignature(md->GetLoaderAllocator(), &amTracker);

MetaSig msig(md);
Signature calliSig = BuildResumptionStubCalliSignature(msig, md->GetMethodTable(), md->GetLoaderAllocator(), &amTracker);

SigTypeContext emptyCtx;
ILStubLinker sl(md->GetModule(), stubSig, &emptyCtx, NULL, ILSTUB_LINKER_FLAG_NONE);

ILCodeStream* pCode = sl.NewCodeStream(ILStubLinker::kDispatch);

pCode->EmitLDARG(0);
pCode->EmitCALL(METHOD__ASYNC_HELPERS__SET_ASYNC_CALL_CONTINUATION_ARG, 1, 0);

int numArgs = 0;

if (msig.HasThis())
Expand All @@ -14683,18 +14716,6 @@ CORINFO_METHOD_HANDLE CEEJitInfo::getAsyncResumptionStub(void** entryPoint)
numArgs++;
}

#ifndef TARGET_X86
if (msig.HasGenericContextArg())
{
pCode->EmitLDC(0);
numArgs++;
}

// Continuation
pCode->EmitLDARG(0);
numArgs++;
#endif

msig.Reset();
CorElementType ty;
while ((ty = msig.NextArg()) != ELEMENT_TYPE_END)
Expand All @@ -14707,50 +14728,7 @@ CORINFO_METHOD_HANDLE CEEJitInfo::getAsyncResumptionStub(void** entryPoint)
numArgs++;
}

#ifdef TARGET_X86
// Continuation
pCode->EmitLDARG(0);

if (msig.HasGenericContextArg())
{
pCode->EmitLDC(0);
numArgs++;
}

numArgs++;
#endif

#ifdef FEATURE_TIERED_COMPILATION
// Resumption stubs are uniquely coupled to the code version (since the
// continuation is), so we need to make sure we always keep calling the
// same version here.
PrepareCodeConfig* config = GetThread()->GetCurrentPrepareCodeConfig();
NativeCodeVersion ncv = config->GetCodeVersion();
if (ncv.GetOptimizationTier() == NativeCodeVersion::OptimizationTier1OSR)
{
#ifdef FEATURE_ON_STACK_REPLACEMENT
// The OSR version needs to resume in the tier0 version. The tier0
Copy link
Member

Choose a reason for hiding this comment

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

This special-case needs to move to GetCallInfo or getFunctionEntryPoint

// version will handle setting up the frame that the OSR version
// expects and then delegating back into the OSR version (knowing to do
// so through information stored in the continuation).
_ASSERTE(m_pPatchpointInfoFromRuntime != NULL);
pCode->EmitLDC((DWORD_PTR)m_pPatchpointInfoFromRuntime->GetTier0EntryPoint());
#else // !FEATURE_ON_STACK_REPLACEMENT
_ASSERTE(!"Unexpected optimization tier with OSR disabled");
#endif // FEATURE_ON_STACK_REPLACEMENT
}
else
#endif // FEATURE_TIERED_COMPILATION
{
{
m_finalCodeAddressSlot = (PCODE*)amTracker.Track(m_pMethodBeingCompiled->GetLoaderAllocator()->GetHighFrequencyHeap()->AllocMem(S_SIZE_T(sizeof(PCODE))));
}

pCode->EmitLDC((DWORD_PTR)m_finalCodeAddressSlot);
pCode->EmitLDIND_I();
}

pCode->EmitCALLI(pCode->GetSigToken(calliSig.GetRawSig(), calliSig.GetRawSigLen()), numArgs, msig.IsReturnTypeVoid() ? 0 : 1);
Copy link
Member

Choose a reason for hiding this comment

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

calliSig can be deleted.

pCode->EmitCALL(pCode->GetToken(md), numArgs, msig.IsReturnTypeVoid() ? 0 : 1);

DWORD resultLoc = UINT_MAX;
TypeHandle resultTypeHnd;
Expand Down Expand Up @@ -14799,6 +14777,8 @@ CORINFO_METHOD_HANDLE CEEJitInfo::getAsyncResumptionStub(void** entryPoint)

const char* optimizationTierName = "UnknownTier";
#ifdef FEATURE_TIERED_COMPILATION
PrepareCodeConfig* config = GetThread()->GetCurrentPrepareCodeConfig();
NativeCodeVersion ncv = config->GetCodeVersion();
switch (ncv.GetOptimizationTier())
{
case NativeCodeVersion::OptimizationTier0: optimizationTierName = "Tier0"; break;
Expand Down
6 changes: 6 additions & 0 deletions src/coreclr/vm/method.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -2957,6 +2957,12 @@ class DynamicMethodDesc : public StoredSigMethodDesc
_ASSERTE(IsILStub());
return GetILStubType() == DynamicMethodDesc::StubDelegateShuffleThunk;
}
bool IsAsyncResumptionStub() const
{
LIMITED_METHOD_DAC_CONTRACT;
_ASSERTE(IsILStub());
return GetILStubType() == DynamicMethodDesc::StubAsyncResume;
}

// Whether the stub takes a context argument that is an interop MethodDesc.
// See RequiresMDContextArg() for the non-stub version.
Expand Down
Loading