Skip to content
Merged
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
1 change: 1 addition & 0 deletions src/coreclr/inc/corinfo.h
Original file line number Diff line number Diff line change
Expand Up @@ -1476,6 +1476,7 @@ 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
32 changes: 12 additions & 20 deletions src/coreclr/interpreter/compiler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6739,6 +6739,14 @@ int InterpCompiler::ApplyLdftnDelegateCtorPeep(const uint8_t* ip, OpcodePeepElem
return -1;
}

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);
return m_resolvedAsyncCallToken.hMethod != NULL;
}

bool InterpCompiler::IsRuntimeAsyncCall(const uint8_t* ip, OpcodePeepElement* pattern, void** ppComputedInfo)
{
CORINFO_RESOLVED_TOKEN awaitResolvedToken;
Expand All @@ -6749,8 +6757,7 @@ bool InterpCompiler::IsRuntimeAsyncCall(const uint8_t* ip, OpcodePeepElement* pa
return false;
}

ResolveToken(getU4LittleEndian(ip + 1), CORINFO_TOKENKIND_Await, &m_resolvedAsyncCallToken);
if (m_resolvedAsyncCallToken.hMethod == NULL)
if (!ResolveAsyncCallToken(ip))
{
return false;
}
Expand Down Expand Up @@ -6818,12 +6825,7 @@ bool InterpCompiler::IsRuntimeAsyncCallConfigureAwaitTask(const uint8_t* ip, Opc
return false;
}

ResolveToken(getU4LittleEndian(ip + 1), CORINFO_TOKENKIND_Await, &m_resolvedAsyncCallToken);
if (m_resolvedAsyncCallToken.hMethod == NULL)
{
return false;
}
return true;
return ResolveAsyncCallToken(ip);
}

bool InterpCompiler::IsRuntimeAsyncCallConfigureAwaitValueTaskExactStLoc(const uint8_t* ip, OpcodePeepElement* pattern, void** ppComputedInfo)
Expand Down Expand Up @@ -6893,12 +6895,7 @@ bool InterpCompiler::IsRuntimeAsyncCallConfigureAwaitValueTaskExactStLoc(const u
return false;
}

ResolveToken(getU4LittleEndian(ip + 1), CORINFO_TOKENKIND_Await, &m_resolvedAsyncCallToken);
if (m_resolvedAsyncCallToken.hMethod == NULL)
{
return false;
}
return true;
return ResolveAsyncCallToken(ip);
}

bool InterpCompiler::IsRuntimeAsyncCallConfigureAwaitValueTask(const uint8_t* ip, OpcodePeepElement* pattern, void** ppComputedInfo)
Expand Down Expand Up @@ -6931,12 +6928,7 @@ bool InterpCompiler::IsRuntimeAsyncCallConfigureAwaitValueTask(const uint8_t* ip
return false;
}

ResolveToken(getU4LittleEndian(ip + 1), CORINFO_TOKENKIND_Await, &m_resolvedAsyncCallToken);
if (m_resolvedAsyncCallToken.hMethod == NULL)
{
return false;
}
return true;
return ResolveAsyncCallToken(ip);
}

int InterpCompiler::ApplyConvRUnR4Peep(const uint8_t* ip, OpcodePeepElement* peep, void* computedInfo)
Expand Down
1 change: 1 addition & 0 deletions src/coreclr/interpreter/compiler.h
Original file line number Diff line number Diff line change
Expand Up @@ -927,6 +927,7 @@ class InterpCompiler
bool IsLdftnDelegateCtorPeep(const uint8_t* ip, OpcodePeepElement* peep, void** outComputedInfo);
int ApplyLdftnDelegateCtorPeep(const uint8_t* ip, OpcodePeepElement* peep, void* computedInfo);

bool ResolveAsyncCallToken(const uint8_t* ip);
enum class ContinuationContextHandling : uint8_t
{
ContinueOnCapturedContext,
Expand Down
16 changes: 12 additions & 4 deletions src/coreclr/jit/importer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9009,6 +9009,8 @@ void Compiler::impImportBlockCode(BasicBlock* block)
codeAddrAfterMatch = impMatchTaskAwaitPattern(codeAddr, codeEndp, &configVal, &awaitOffset);
if (codeAddrAfterMatch != nullptr)
{
JITDUMP("Recognized await%s\n", configVal == 0 ? " (with ConfigureAwait(false))" : "");

isAwait = true;
prefixFlags |= PREFIX_IS_TASK_AWAIT;
if (configVal != 0)
Expand All @@ -9020,7 +9022,8 @@ void Compiler::impImportBlockCode(BasicBlock* block)

if (isAwait)
{
_impResolveToken(CORINFO_TOKENKIND_Await);
_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.
Expand All @@ -9030,10 +9033,15 @@ void Compiler::impImportBlockCode(BasicBlock* block)
}
else
{
// This can happen in rare 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. Treat
// that as a regular call that is Awaited
// 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.
// It can also happen generally if the VM does not think using the async entry point
// is worth it. Treat these as a regular call that is Awaited.
_impResolveToken(CORINFO_TOKENKIND_Method);
prefixFlags &= ~(PREFIX_IS_TASK_AWAIT | PREFIX_TASK_AWAIT_CONTINUE_ON_CAPTURED_CONTEXT);
isAwait = false;

JITDUMP("No async variant provided by VM, treating as regular call that is awaited\n");
}
}
else
Expand Down
40 changes: 38 additions & 2 deletions src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1776,12 +1776,34 @@ private object GetRuntimeDeterminedObjectForToken(ref CORINFO_RESOLVED_TOKEN pRe
if (pResolvedToken.tokenType == CorInfoTokenKind.CORINFO_TOKENKIND_Newarr)
result = ((TypeDesc)result).MakeArrayType();

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

return result;
}

private bool IsCallEffectivelyDirect(MethodDesc method)
{
if (!method.IsVirtual)
{
return true;
}

// Final/sealed has no meaning for interfaces, but might let us devirtualize otherwise
if (method.OwningType.IsInterface)
{
return false;
}

// Check if we can devirt per metadata
if (method.IsFinal || method.OwningType.IsSealed())
{
return true;
}

return false;
}

private static object GetRuntimeDeterminedObjectForToken(MethodILScope methodIL, object typeOrMethodContext, mdToken token)
{
object result = ResolveTokenInScope(methodIL, typeOrMethodContext, token);
Expand Down Expand Up @@ -1866,7 +1888,7 @@ private void resolveToken(ref CORINFO_RESOLVED_TOKEN pResolvedToken)
_compilation.NodeFactory.MetadataManager.GetDependenciesDueToAccess(ref _additionalDependencies, _compilation.NodeFactory, (MethodIL)methodIL, method);
#endif

if (pResolvedToken.tokenType == CorInfoTokenKind.CORINFO_TOKENKIND_Await)
if (pResolvedToken.tokenType is CorInfoTokenKind.CORINFO_TOKENKIND_Await or CorInfoTokenKind.CORINFO_TOKENKIND_AwaitVirtual)
{
// 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 @@ -1876,6 +1898,20 @@ 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 || IsCallEffectivelyDirect(method);
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
1 change: 1 addition & 0 deletions src/coreclr/tools/Common/JitInterface/CorInfoTypes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1469,6 +1469,7 @@ 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 @@ -1363,23 +1363,16 @@ private void getCallInfo(ref CORINFO_RESOLVED_TOKEN pResolvedToken, CORINFO_RESO
else
{
// We can devirtualize the callvirt if the method is not virtual to begin with
bool canDevirt = !targetMethod.IsVirtual;

// Final/sealed has no meaning for interfaces, but might let us devirtualize otherwise
if (!canDevirt && !targetMethod.OwningType.IsInterface)
bool canDevirt = IsCallEffectivelyDirect(targetMethod);

// We might be able to devirt based on whole program view
if (!canDevirt
// Do not devirt if devirtualization would need a generic dictionary entry that we didn't predict
// during scanning (i.e. compiling a shared method body and we need to call another shared body
// with a method generic dictionary argument).
&& (!pResult->exactContextNeedsRuntimeLookup || !targetMethod.GetCanonMethodTarget(CanonicalFormKind.Specific).RequiresInstMethodDescArg()))
{
// Check if we can devirt per metadata
canDevirt = targetMethod.IsFinal || targetMethod.OwningType.IsSealed();

// We might be able to devirt based on whole program view
if (!canDevirt
// Do not devirt if devirtualization would need a generic dictionary entry that we didn't predict
// during scanning (i.e. compiling a shared method body and we need to call another shared body
// with a method generic dictionary argument).
&& (!pResult->exactContextNeedsRuntimeLookup || !targetMethod.GetCanonMethodTarget(CanonicalFormKind.Specific).RequiresInstMethodDescArg()))
{
canDevirt = _compilation.IsEffectivelySealed(targetMethod);
}
canDevirt = _compilation.IsEffectivelySealed(targetMethod);
}

if (canDevirt)
Expand Down
38 changes: 32 additions & 6 deletions src/coreclr/vm/jitinterface.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1081,13 +1081,39 @@ void CEEInfo::resolveToken(/* IN, OUT */ CORINFO_RESOLVED_TOKEN * pResolvedToken
break;

case 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.
// return NULL, so that caller would re-resolve as a regular method call
pMD = pMD->ReturnsTaskOrValueTask() ?
pMD->GetAsyncVariant(/*allowInstParam*/FALSE):
NULL;
case CORINFO_TOKENKIND_AwaitVirtual:
{
// 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.
// return NULL, so that caller would re-resolve as a regular method call
bool allowAsyncVariant = pMD->ReturnsTaskOrValueTask();
if (allowAsyncVariant)
{
bool isDirect = tokenType == CORINFO_TOKENKIND_Await || pMD->IsStatic();
if (!isDirect)
{
DWORD attrs = pMD->GetAttrs();
if (pMD->GetMethodTable()->IsInterface())
{
isDirect = !IsMdVirtual(attrs);
}
else
{
isDirect = !IsMdVirtual(attrs) || IsMdFinal(attrs) || pMD->GetMethodTable()->IsSealed();
}
}

if (isDirect && !pMD->IsAsyncThunkMethod())
{
// 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;
}
}

pMD = allowAsyncVariant ? pMD->GetAsyncVariant(/*allowInstParam*/FALSE) : NULL;
}
break;

default:
Expand Down
Loading