diff --git a/src/coreclr/tools/Common/Compiler/MethodExtensions.cs b/src/coreclr/tools/Common/Compiler/MethodExtensions.cs index 8494e5bb552037..5949052cb51032 100644 --- a/src/coreclr/tools/Common/Compiler/MethodExtensions.cs +++ b/src/coreclr/tools/Common/Compiler/MethodExtensions.cs @@ -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; + } + /// /// Determines whether a method uses the async calling convention. /// Returns true for async variants (compiler-generated wrappers around Task-returning methods) diff --git a/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs b/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs index e0943d3fb6c99d..f82c3914a4cf57 100644 --- a/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs +++ b/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs @@ -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); @@ -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 diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/IL/ILImporter.Scanner.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/IL/ILImporter.Scanner.cs index 2b61264f1fbe33..4394d08ad8240b 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/IL/ILImporter.Scanner.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/IL/ILImporter.Scanner.cs @@ -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); @@ -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; } diff --git a/src/coreclr/tools/aot/ILCompiler.RyuJit/JitInterface/CorInfoImpl.RyuJit.cs b/src/coreclr/tools/aot/ILCompiler.RyuJit/JitInterface/CorInfoImpl.RyuJit.cs index a0c3129a49241f..052d6572e481fa 100644 --- a/src/coreclr/tools/aot/ILCompiler.RyuJit/JitInterface/CorInfoImpl.RyuJit.cs +++ b/src/coreclr/tools/aot/ILCompiler.RyuJit/JitInterface/CorInfoImpl.RyuJit.cs @@ -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 diff --git a/src/tests/async/staticvirtual/staticvirtual.cs b/src/tests/async/staticvirtual/staticvirtual.cs new file mode 100644 index 00000000000000..d74644de6f3e59 --- /dev/null +++ b/src/tests/async/staticvirtual/staticvirtual.cs @@ -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() where T : IHaveStaticVirtuals => await T.DoTask(); + + [Fact] + public static void TestEntryPoint() + { + CallDoTask().Wait(); + } +} diff --git a/src/tests/async/staticvirtual/staticvirtual.csproj b/src/tests/async/staticvirtual/staticvirtual.csproj new file mode 100644 index 00000000000000..3fc50cde4b3443 --- /dev/null +++ b/src/tests/async/staticvirtual/staticvirtual.csproj @@ -0,0 +1,5 @@ + + + + +