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
22 changes: 22 additions & 0 deletions src/coreclr/tools/Common/Compiler/MethodExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,28 @@ public static bool ReturnsTaskOrValueTask(this MethodSignature method)
return false;
}

public static bool IsCallEffectivelyDirect(this 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;
}

/// <summary>
/// Determines whether a method uses the async calling convention.
/// Returns true for async variants (compiler-generated wrappers around Task-returning methods)
Expand Down
24 changes: 1 addition & 23 deletions src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1784,28 +1784,6 @@ private object GetRuntimeDeterminedObjectForToken(ref CORINFO_RESOLVED_TOKEN pRe
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 @@ -1903,7 +1881,7 @@ private void resolveToken(ref CORINFO_RESOLVED_TOKEN pResolvedToken)
#if !READYTORUN
if (allowAsyncVariant)
{
bool isDirect = pResolvedToken.tokenType == CorInfoTokenKind.CORINFO_TOKENKIND_Await || IsCallEffectivelyDirect(method);
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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -476,9 +476,27 @@ private void ImportCall(ILOpcode opcode, int token)

// If this is the task await pattern, we're actually going to call the variant
// so switch our focus to the variant.
if (method.GetTypicalMethodDefinition().Signature.ReturnsTaskOrValueTask()
&& !method.OwningType.IsDelegate
&& MatchTaskAwaitPattern())

// 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.
bool allowAsyncVariant = method.GetTypicalMethodDefinition().Signature.ReturnsTaskOrValueTask();

// 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);
method = _factory.TypeSystemContext.GetAsyncVariantMethod(method);
Expand Down Expand Up @@ -688,9 +706,7 @@ private void ImportCall(ILOpcode opcode, int token)
}
else
{
if (!targetMethod.IsVirtual ||
// Final/sealed has no meaning for interfaces, but lets us devirtualize otherwise
(!targetMethod.OwningType.IsInterface && (targetMethod.IsFinal || targetMethod.OwningType.IsSealed())))
if (targetMethod.IsCallEffectivelyDirect())
{
directCall = true;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1363,7 +1363,7 @@ 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 = IsCallEffectivelyDirect(targetMethod);
bool canDevirt = targetMethod.IsCallEffectivelyDirect();

// We might be able to devirt based on whole program view
if (!canDevirt
Expand Down
28 changes: 28 additions & 0 deletions src/tests/async/staticvirtual/staticvirtual.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
using Xunit;

public class StaticVirtual
{
interface IHaveStaticVirtuals
{
static abstract Task DoTask();
}

class ClassWithStaticVirtuals : IHaveStaticVirtuals
{
public static async Task DoTask() => await Task.Yield();
}

static async Task CallDoTask<T>() where T : IHaveStaticVirtuals => await T.DoTask();

[Fact]
public static void TestEntryPoint()
{
CallDoTask<ClassWithStaticVirtuals>().Wait();
}
}
5 changes: 5 additions & 0 deletions src/tests/async/staticvirtual/staticvirtual.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<Project Sdk="Microsoft.NET.Sdk.IL">
<ItemGroup>
<Compile Include="$(MSBuildProjectName).cs" />
</ItemGroup>
</Project>
Loading