Skip to content

Commit 9155f26

Browse files
Virtual methods with runtime async (#121443)
This makes virtual methods work with runtime async. Depends on #121438. Runtime async methods can be virtually called two ways: as Task-returning, or as runtime-async. We can even end up in situation where Task-returning non-runtime-async virtual methods get called as runtime-async. The easiest way to solve this is to give each Task-returning method two separate virtual slots. We still track the slot use so unused slots will not get generated. The `VirtualMethodAlgorithm` abstraction that we added for universal shared generics comes in handy because it lets us centralize where all of this work happens. As a result, we don't need to teach pretty much any node dealing with virtuals about runtime async, it just falls out. This doesn't make generic virtual methods yet, those need more fixes.
1 parent 029aadd commit 9155f26

File tree

7 files changed

+159
-10
lines changed

7 files changed

+159
-10
lines changed

src/coreclr/tools/Common/Compiler/AsyncMethodVariant.cs

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,6 @@ private MethodSignature InitializeSignature()
4343

4444
public override MethodDesc GetCanonMethodTarget(CanonicalFormKind kind)
4545
{
46-
// We should not be calling GetCanonMethodTarget on generic definitions of anything
47-
// and this MethodDesc is a generic definition.
48-
Debug.Assert(!HasInstantiation && !OwningType.HasInstantiation);
4946
return this;
5047
}
5148

@@ -77,12 +74,9 @@ protected override int CompareToImpl(MethodDesc other, TypeSystemComparer compar
7774

7875
public static class AsyncMethodVariantExtensions
7976
{
80-
/// <summary>
81-
/// Returns true if this MethodDesc is an AsyncMethodVariant, which should not escape the jit interface.
82-
/// </summary>
8377
public static bool IsAsyncVariant(this MethodDesc method)
8478
{
85-
return method is AsyncMethodVariant;
79+
return method.GetTypicalMethodDefinition() is AsyncMethodVariant;
8680
}
8781
}
8882
}

src/coreclr/tools/Common/Compiler/CompilerTypeSystemContext.Async.cs

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4+
using System.Collections.Generic;
5+
46
using Internal.IL;
57
using Internal.TypeSystem;
68
using Internal.TypeSystem.Ecma;
@@ -11,6 +13,141 @@ namespace ILCompiler
1113
{
1214
public partial class CompilerTypeSystemContext
1315
{
16+
private sealed class AsyncAwareVirtualMethodResolutionAlgorithm : MetadataVirtualMethodAlgorithm
17+
{
18+
private readonly CompilerTypeSystemContext _context;
19+
20+
public AsyncAwareVirtualMethodResolutionAlgorithm(CompilerTypeSystemContext context)
21+
=> _context = context;
22+
23+
private MethodDesc DecomposeAsyncVariant(MethodDesc method, out bool isAsyncVariant)
24+
{
25+
isAsyncVariant = method.IsAsyncVariant();
26+
return isAsyncVariant ? _context.GetTargetOfAsyncVariantMethod(method) : method;
27+
}
28+
29+
public override MethodDesc FindVirtualFunctionTargetMethodOnObjectType(MethodDesc targetMethod, TypeDesc objectType)
30+
{
31+
targetMethod = DecomposeAsyncVariant(targetMethod, out bool isAsyncSlot);
32+
MethodDesc result = base.FindVirtualFunctionTargetMethodOnObjectType(targetMethod, objectType);
33+
if (result != null && isAsyncSlot)
34+
result = _context.GetAsyncVariantMethod(result);
35+
36+
return result;
37+
}
38+
39+
public override DefaultInterfaceMethodResolution ResolveInterfaceMethodToDefaultImplementationOnType(MethodDesc interfaceMethod, TypeDesc currentType, out MethodDesc impl)
40+
{
41+
interfaceMethod = DecomposeAsyncVariant(interfaceMethod, out bool isAsyncSlot);
42+
DefaultInterfaceMethodResolution result = base.ResolveInterfaceMethodToDefaultImplementationOnType(interfaceMethod, currentType, out impl);
43+
if (impl != null && isAsyncSlot)
44+
impl = _context.GetAsyncVariantMethod(impl);
45+
46+
return result;
47+
}
48+
49+
public override MethodDesc ResolveInterfaceMethodToStaticVirtualMethodOnType(MethodDesc interfaceMethod, TypeDesc currentType)
50+
{
51+
interfaceMethod = DecomposeAsyncVariant(interfaceMethod, out bool isAsyncSlot);
52+
MethodDesc result = base.ResolveInterfaceMethodToStaticVirtualMethodOnType(interfaceMethod, currentType);
53+
if (result != null && isAsyncSlot)
54+
result = _context.GetAsyncVariantMethod(result);
55+
56+
return result;
57+
}
58+
public override MethodDesc ResolveInterfaceMethodToVirtualMethodOnType(MethodDesc interfaceMethod, TypeDesc currentType)
59+
{
60+
interfaceMethod = DecomposeAsyncVariant(interfaceMethod, out bool isAsyncSlot);
61+
MethodDesc result = base.ResolveInterfaceMethodToVirtualMethodOnType(interfaceMethod, currentType);
62+
if (result != null && isAsyncSlot)
63+
result = _context.GetAsyncVariantMethod(result);
64+
65+
return result;
66+
}
67+
public override DefaultInterfaceMethodResolution ResolveVariantInterfaceMethodToDefaultImplementationOnType(MethodDesc interfaceMethod, TypeDesc currentType, out MethodDesc impl)
68+
{
69+
interfaceMethod = DecomposeAsyncVariant(interfaceMethod, out bool isAsyncSlot);
70+
DefaultInterfaceMethodResolution result = base.ResolveVariantInterfaceMethodToDefaultImplementationOnType(interfaceMethod, currentType, out impl);
71+
if (impl != null && isAsyncSlot)
72+
impl = _context.GetAsyncVariantMethod(impl);
73+
74+
return result;
75+
}
76+
public override MethodDesc ResolveVariantInterfaceMethodToStaticVirtualMethodOnType(MethodDesc interfaceMethod, TypeDesc currentType)
77+
{
78+
interfaceMethod = DecomposeAsyncVariant(interfaceMethod, out bool isAsyncSlot);
79+
MethodDesc result = base.ResolveVariantInterfaceMethodToStaticVirtualMethodOnType(interfaceMethod, currentType);
80+
if (result != null && isAsyncSlot)
81+
result = _context.GetAsyncVariantMethod(result);
82+
83+
return result;
84+
}
85+
public override MethodDesc ResolveVariantInterfaceMethodToVirtualMethodOnType(MethodDesc interfaceMethod, TypeDesc currentType)
86+
{
87+
interfaceMethod = DecomposeAsyncVariant(interfaceMethod, out bool isAsyncSlot);
88+
MethodDesc result = base.ResolveVariantInterfaceMethodToVirtualMethodOnType(interfaceMethod, currentType);
89+
if (result != null && isAsyncSlot)
90+
result = _context.GetAsyncVariantMethod(result);
91+
92+
return result;
93+
}
94+
95+
public override IEnumerable<MethodDesc> ComputeAllVirtualSlots(TypeDesc type)
96+
{
97+
foreach (MethodDesc method in base.ComputeAllVirtualSlots(type))
98+
{
99+
yield return method;
100+
101+
// We create an async variant slot for any Task-returning method, not just runtime-async.
102+
// This is not a problem in practice because the slot is still subject to dependency
103+
// analysis and if not used, will not be generated.
104+
//
105+
// The reason why we need it is this:
106+
//
107+
// interface IFoo
108+
// {
109+
// [RuntimeAsyncMethodGeneration(true)]
110+
// Task Method();
111+
// }
112+
//
113+
// class Base
114+
// {
115+
// [RuntimeAsyncMethodGeneration(false)]
116+
// public virtual Task Method();
117+
// }
118+
//
119+
// class Derived : Base, IFoo
120+
// {
121+
// // Q: The runtime-async implementation for IFoo.Method
122+
// // comes from Base. However Base was not runtime-async and we
123+
// // didn't know about IFoo in Base either. Who has the slot?
124+
// // A: Base has the runtime-async slot, despite the method not being runtime-async.
125+
// }
126+
if (method.GetTypicalMethodDefinition().Signature.ReturnsTaskOrValueTask())
127+
yield return _context.GetAsyncVariantMethod(method);
128+
}
129+
}
130+
}
131+
132+
public MethodDesc GetTargetOfAsyncVariantMethod(MethodDesc asyncVariantMethod)
133+
{
134+
var asyncMethodVariantDefinition = (AsyncMethodVariant)asyncVariantMethod.GetTypicalMethodDefinition();
135+
MethodDesc result = asyncMethodVariantDefinition.Target;
136+
137+
// If there are generics involved, we need to specialize
138+
if (asyncVariantMethod != asyncMethodVariantDefinition)
139+
{
140+
TypeDesc owningType = asyncVariantMethod.OwningType;
141+
if (owningType != asyncMethodVariantDefinition.OwningType)
142+
result = GetMethodForInstantiatedType(result, (InstantiatedType)owningType);
143+
144+
if (asyncVariantMethod.HasInstantiation && !asyncVariantMethod.IsMethodDefinition)
145+
result = GetInstantiatedMethod(result, asyncVariantMethod.Instantiation);
146+
}
147+
148+
return result;
149+
}
150+
14151
public MethodDesc GetAsyncVariantMethod(MethodDesc taskReturningMethod)
15152
{
16153
Debug.Assert(taskReturningMethod.Signature.ReturnsTaskOrValueTask());

src/coreclr/tools/Common/Compiler/CompilerTypeSystemContext.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ namespace ILCompiler
1717
public partial class CompilerTypeSystemContext : MetadataTypeSystemContext, IMetadataStringDecoderProvider
1818
{
1919
private readonly MetadataRuntimeInterfacesAlgorithm _metadataRuntimeInterfacesAlgorithm = new MetadataRuntimeInterfacesAlgorithm();
20-
private readonly MetadataVirtualMethodAlgorithm _virtualMethodAlgorithm = new MetadataVirtualMethodAlgorithm();
2120

2221
private MetadataStringDecoder _metadataStringDecoder;
2322

src/coreclr/tools/Common/TypeSystem/IL/NativeAotILProvider.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -378,8 +378,7 @@ public override MethodIL GetMethodIL(MethodDesc method)
378378
}
379379
else
380380
{
381-
// TODO: Emit thunk with async calling convention
382-
throw new NotImplementedException();
381+
return AsyncThunkILEmitter.EmitAsyncMethodThunk(asyncVariantImpl, asyncVariantImpl.Target);
383382
}
384383
}
385384
else

src/coreclr/tools/Common/TypeSystem/IL/Stubs/AsyncThunks.cs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,5 +36,19 @@ public static MethodIL EmitTaskReturningThunk(MethodDesc taskReturningMethod, Me
3636

3737
return emitter.Link(taskReturningMethod);
3838
}
39+
40+
public static MethodIL EmitAsyncMethodThunk(MethodDesc asyncMethod, MethodDesc taskReturningMethod)
41+
{
42+
TypeSystemContext context = asyncMethod.Context;
43+
44+
var emitter = new ILEmitter();
45+
var codestream = emitter.NewCodeStream();
46+
47+
// TODO: match EmitAsyncMethodThunk in CoreCLR VM
48+
49+
codestream.EmitCallThrowHelper(emitter, context.GetHelperEntryPoint("ThrowHelpers"u8, "ThrowNotSupportedException"u8));
50+
51+
return emitter.Link(asyncMethod);
52+
}
3953
}
4054
}

src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/CompilerTypeSystemContext.Aot.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ public SharedGenericsConfiguration GenericsConfig
3939
private readonly Int128FieldLayoutAlgorithm _int128FieldLayoutAlgorithm;
4040
private readonly TypeWithRepeatedFieldsFieldLayoutAlgorithm _typeWithRepeatedFieldsFieldLayoutAlgorithm;
4141

42+
private readonly AsyncAwareVirtualMethodResolutionAlgorithm _virtualMethodAlgorithm;
43+
4244
private TypeDesc[] _arrayOfTInterfaces;
4345
private TypeDesc[] _arrayEnumeratorOfTInterfaces;
4446
private ArrayOfTRuntimeInterfacesAlgorithm _arrayOfTRuntimeInterfacesAlgorithm;
@@ -53,6 +55,8 @@ public CompilerTypeSystemContext(TargetDetails details, SharedGenericsMode gener
5355
{
5456
_genericsMode = genericsMode;
5557

58+
_virtualMethodAlgorithm = new AsyncAwareVirtualMethodResolutionAlgorithm(this);
59+
5660
_vectorOfTFieldLayoutAlgorithm = new VectorOfTFieldLayoutAlgorithm(_metadataFieldLayoutAlgorithm);
5761
_vectorFieldLayoutAlgorithm = new VectorFieldLayoutAlgorithm(_metadataFieldLayoutAlgorithm);
5862
_int128FieldLayoutAlgorithm = new Int128FieldLayoutAlgorithm(_metadataFieldLayoutAlgorithm);

src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunCompilerContext.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ namespace ILCompiler
1111
{
1212
partial class CompilerTypeSystemContext
1313
{
14+
private readonly MetadataVirtualMethodAlgorithm _virtualMethodAlgorithm = new MetadataVirtualMethodAlgorithm();
15+
1416
public CompilerTypeSystemContext(TargetDetails details, SharedGenericsMode genericsMode)
1517
: base(details)
1618
{

0 commit comments

Comments
 (0)