Skip to content

Commit 3927844

Browse files
Support async generic virtual methods (#121524)
* Since generic virtual method dispatch is based on `RuntimeMethodHandle`, we need a `RuntimeMethodHandle` for async variants. Emit the bit in the compiler, at runtime (when type loading), and read it when needed. Reflection stack should never see this `RuntimeMethodHandle`. * Add `AsyncVariant` property on `MethodDesc` (since runtime type system does this for unboxing methods too). * Add a flag that distinguishes async variants in native layout. This is not specific to GVMs, it also covers other cases of runtime generic dictionary building, such as `MakeGenericMethod`. * Add a flag that distinguishes these in generic method dictionary hashtables (both static and built at runtime) * Lots of piping through the extra bool
1 parent 5574021 commit 3927844

23 files changed

+220
-56
lines changed

src/coreclr/nativeaot/System.Private.CoreLib/src/System/RuntimeMethodHandle.cs

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ public unsafe bool Equals(RuntimeMethodHandle handle)
4949
return false;
5050
if (!thisInfo->Handle.Equals(thatInfo->Handle))
5151
return false;
52-
if (thisInfo->NumGenericArgs != thatInfo->NumGenericArgs)
52+
if (thisInfo->_numGenericArgsAndFlag != thatInfo->_numGenericArgsAndFlag)
5353
return false;
5454

5555
RuntimeTypeHandle* thisFirstArg = &thisInfo->FirstArgument;
@@ -123,7 +123,19 @@ public struct MethodHandleInfo
123123
{
124124
public RuntimeTypeHandle DeclaringType;
125125
public MethodHandle Handle;
126-
public int NumGenericArgs;
126+
internal int _numGenericArgsAndFlag;
127127
public RuntimeTypeHandle FirstArgument;
128+
129+
public int NumGenericArgs
130+
{
131+
get => _numGenericArgsAndFlag & ~RuntimeMethodHandleConstants.IsAsyncVariant;
132+
set => _numGenericArgsAndFlag = (_numGenericArgsAndFlag & RuntimeMethodHandleConstants.IsAsyncVariant) | value;
133+
}
134+
135+
public bool IsAsyncVariant
136+
{
137+
get => (_numGenericArgsAndFlag & RuntimeMethodHandleConstants.IsAsyncVariant) != 0;
138+
set => _numGenericArgsAndFlag = value ? _numGenericArgsAndFlag | RuntimeMethodHandleConstants.IsAsyncVariant : _numGenericArgsAndFlag & ~RuntimeMethodHandleConstants.IsAsyncVariant;
139+
}
128140
}
129141
}

src/coreclr/nativeaot/System.Private.Reflection.Execution/src/Internal/Reflection/Execution/ExecutionEnvironmentImplementation.MappingTables.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -695,7 +695,8 @@ private unsafe bool TryGetMethodForOriginalLdFtnResult_InvokeMap_Inner(NativeFor
695695
private static unsafe bool TryGetMethodForOriginalLdFtnResult_GenericMethodWithInstantiationArgument(IntPtr instantiationArgument, ref RuntimeTypeHandle declaringTypeHandle, out QMethodDefinition methodHandle, out RuntimeTypeHandle[] genericMethodTypeArgumentHandles)
696696
{
697697
MethodNameAndSignature nameAndSig;
698-
bool success = TypeLoaderEnvironment.Instance.TryGetGenericMethodComponents(instantiationArgument, out declaringTypeHandle, out nameAndSig, out genericMethodTypeArgumentHandles);
698+
bool success = TypeLoaderEnvironment.Instance.TryGetGenericMethodComponents(instantiationArgument, out declaringTypeHandle, out nameAndSig, out genericMethodTypeArgumentHandles, out bool isAsyncVariant);
699+
Debug.Assert(!isAsyncVariant, "Async variants should not be visible to reflection");
699700
if (success)
700701
{
701702
if (TypeLoaderEnvironment.Instance.TryGetMetadataForTypeMethodNameAndSignature(declaringTypeHandle, nameAndSig, out methodHandle))

src/coreclr/nativeaot/System.Private.TypeLoader/src/Internal/Runtime/TypeLoader/GenericDictionaryCell.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -328,7 +328,8 @@ internal override unsafe IntPtr Create(TypeBuilder builder)
328328
RuntimeMethodHandle handle = TypeLoaderEnvironment.Instance.GetRuntimeMethodHandleForComponents(
329329
builder.GetRuntimeTypeHandle(Method.OwningType),
330330
Method.NameAndSignature.Handle,
331-
genericArgHandles);
331+
genericArgHandles,
332+
Method.AsyncVariant);
332333

333334
return *(IntPtr*)&handle;
334335
}

src/coreclr/nativeaot/System.Private.TypeLoader/src/Internal/Runtime/TypeLoader/NativeLayoutInfoLoadContext.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -189,17 +189,18 @@ internal MethodDesc GetMethod(ref NativeParser parser)
189189
MethodNameAndSignature nameAndSignature = new MethodNameAndSignature(_module.MetadataReader, token.AsHandle().ToMethodHandle(_module.MetadataReader));
190190

191191
bool unboxingStub = (flags & MethodFlags.IsUnboxingStub) != 0;
192+
bool asyncVariant = (flags & MethodFlags.IsAsyncVariant) != 0;
192193

193194
MethodDesc retVal;
194195
if ((flags & MethodFlags.HasInstantiation) != 0)
195196
{
196197
TypeDesc[] typeArguments = GetTypeSequence(ref parser);
197198
Debug.Assert(typeArguments.Length > 0);
198-
retVal = this._typeSystemContext.ResolveGenericMethodInstantiation(unboxingStub, containingType, nameAndSignature, new Instantiation(typeArguments));
199+
retVal = this._typeSystemContext.ResolveGenericMethodInstantiation(unboxingStub, asyncVariant, containingType, nameAndSignature, new Instantiation(typeArguments));
199200
}
200201
else
201202
{
202-
retVal = this._typeSystemContext.ResolveRuntimeMethod(unboxingStub, containingType, nameAndSignature);
203+
retVal = this._typeSystemContext.ResolveRuntimeMethod(unboxingStub, asyncVariant, containingType, nameAndSignature);
203204
}
204205

205206
if ((flags & MethodFlags.HasFunctionPointer) != 0)

src/coreclr/nativeaot/System.Private.TypeLoader/src/Internal/Runtime/TypeLoader/TypeBuilder.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -286,7 +286,7 @@ internal void ParseNativeLayoutInfo(InstantiatedMethod method)
286286
if (!method.UnboxingStub && method.OwningType.IsValueType && !TypeLoaderEnvironment.IsStaticMethodSignature(method.NameAndSignature))
287287
{
288288
// Make it an unboxing stub, note the first parameter which is true
289-
nonTemplateMethod = (InstantiatedMethod)method.Context.ResolveGenericMethodInstantiation(true, (DefType)method.OwningType, method.NameAndSignature, method.Instantiation);
289+
nonTemplateMethod = (InstantiatedMethod)method.Context.ResolveGenericMethodInstantiation(true, method.AsyncVariant, (DefType)method.OwningType, method.NameAndSignature, method.Instantiation);
290290
}
291291

292292
uint nativeLayoutInfoToken;
@@ -802,6 +802,7 @@ private void FinishRuntimeType(TypeDesc type)
802802
_declaringTypeHandle = GetRuntimeTypeHandle(method.OwningType),
803803
_genericMethodArgumentHandles = GetRuntimeTypeHandles(method.Instantiation),
804804
_methodNameAndSignature = method.NameAndSignature,
805+
_isAsyncVariant = method.AsyncVariant,
805806
_methodDictionary = method.RuntimeMethodDictionary
806807
};
807808
}

src/coreclr/nativeaot/System.Private.TypeLoader/src/Internal/Runtime/TypeLoader/TypeLoaderEnvironment.ConstructedGenericMethodsLookup.cs

Lines changed: 34 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
using System.Reflection.Runtime.General;
88
using System.Threading;
99

10+
using Internal.Metadata.NativeFormat;
1011
using Internal.NativeFormat;
1112
using Internal.Runtime.CompilerServices;
1213
using Internal.TypeSystem;
@@ -19,6 +20,7 @@ internal class GenericMethodEntry
1920
{
2021
private int? _hashCode;
2122
public bool _isRegisteredSuccessfully;
23+
public bool _isAsyncVariant;
2224
public IntPtr _methodDictionary;
2325
public RuntimeTypeHandle _declaringTypeHandle;
2426
public MethodNameAndSignature _methodNameAndSignature;
@@ -45,6 +47,9 @@ public virtual bool IsEqualToEntryByComponentsComparison(GenericMethodEntry othe
4547
if (!other._declaringTypeHandle.Equals(_declaringTypeHandle))
4648
return false;
4749

50+
if (_isAsyncVariant != other._isAsyncVariant)
51+
return false;
52+
4853
if (!other._methodNameAndSignature.Equals(_methodNameAndSignature))
4954
return false;
5055

@@ -150,8 +155,14 @@ internal override bool MatchParsedEntry(ref NativeParser entryParser, ref Extern
150155
if (_methodToLookup.OwningType != parsedDeclaringType)
151156
return false;
152157

153-
// Hash table names / sigs are indirected through to the native layout info
154-
MethodNameAndSignature nameAndSignature = TypeLoaderEnvironment.GetMethodNameAndSignatureFromToken(moduleHandle, entryParser.GetUnsigned());
158+
int flagsAndToken = (int)entryParser.GetUnsigned();
159+
bool isAsyncVariant = (flagsAndToken & GenericMethodsHashtableConstants.IsAsyncVariant) != 0;
160+
if (_methodToLookup.AsyncVariant != isAsyncVariant)
161+
return false;
162+
163+
int token = ((int)HandleType.Method << 25) | (flagsAndToken & ~GenericMethodsHashtableConstants.IsAsyncVariant);
164+
165+
MethodNameAndSignature nameAndSignature = TypeLoaderEnvironment.GetMethodNameAndSignatureFromToken(moduleHandle, (uint)token);
155166
if (!_methodToLookup.NameAndSignature.Equals(nameAndSignature))
156167
return false;
157168

@@ -178,6 +189,9 @@ internal override bool MatchGenericMethodEntry(GenericMethodEntry entry)
178189
if (_methodToLookup.OwningType != parsedDeclaringType)
179190
return false;
180191

192+
if (_methodToLookup.AsyncVariant != entry._isAsyncVariant)
193+
return false;
194+
181195
if (!_methodToLookup.NameAndSignature.Equals(entry._methodNameAndSignature))
182196
return false;
183197

@@ -208,11 +222,11 @@ internal bool TryLookupGenericMethodDictionary(GenericMethodLookupData lookupDat
208222
return true;
209223
}
210224

211-
public bool TryGetGenericMethodComponents(IntPtr methodDictionary, out RuntimeTypeHandle declaringType, out MethodNameAndSignature nameAndSignature, out RuntimeTypeHandle[] genericMethodArgumentHandles)
225+
public bool TryGetGenericMethodComponents(IntPtr methodDictionary, out RuntimeTypeHandle declaringType, out MethodNameAndSignature nameAndSignature, out RuntimeTypeHandle[] genericMethodArgumentHandles, out bool isAsyncVariant)
212226
{
213-
if (!TryGetDynamicGenericMethodComponents(methodDictionary, out declaringType, out nameAndSignature, out genericMethodArgumentHandles))
227+
if (!TryGetDynamicGenericMethodComponents(methodDictionary, out declaringType, out nameAndSignature, out genericMethodArgumentHandles, out isAsyncVariant))
214228
{
215-
if (!TryGetStaticGenericMethodComponents(methodDictionary, out declaringType, out nameAndSignature, out genericMethodArgumentHandles))
229+
if (!TryGetStaticGenericMethodComponents(methodDictionary, out declaringType, out nameAndSignature, out genericMethodArgumentHandles, out isAsyncVariant))
216230
return false;
217231
}
218232

@@ -222,8 +236,8 @@ public bool TryGetGenericMethodComponents(IntPtr methodDictionary, out RuntimeTy
222236
public static bool TryGetGenericMethodComponents(IntPtr methodDictionary, out RuntimeTypeHandle declaringType, out RuntimeTypeHandle[] genericMethodArgumentHandles)
223237
{
224238
TypeLoaderEnvironment instance = TypeLoaderEnvironment.InstanceOrNull;
225-
if (instance == null || !instance.TryGetDynamicGenericMethodComponents(methodDictionary, out declaringType, out _, out genericMethodArgumentHandles))
226-
if (!TryGetStaticGenericMethodComponents(methodDictionary, out declaringType, out _, out genericMethodArgumentHandles))
239+
if (instance == null || !instance.TryGetDynamicGenericMethodComponents(methodDictionary, out declaringType, out _, out genericMethodArgumentHandles, out _))
240+
if (!TryGetStaticGenericMethodComponents(methodDictionary, out declaringType, out _, out genericMethodArgumentHandles, out _))
227241
return false;
228242

229243
return true;
@@ -286,7 +300,7 @@ public bool TryGetGenericVirtualMethodPointer(InstantiatedMethod method, out Int
286300
if (!method.UnboxingStub && method.OwningType.IsValueType && !IsStaticMethodSignature(method.NameAndSignature))
287301
{
288302
// Make it an unboxing stub, note the first parameter which is true
289-
nonTemplateMethod = (InstantiatedMethod)method.Context.ResolveGenericMethodInstantiation(true, (DefType)method.OwningType, method.NameAndSignature, method.Instantiation);
303+
nonTemplateMethod = (InstantiatedMethod)method.Context.ResolveGenericMethodInstantiation(true, method.AsyncVariant, (DefType)method.OwningType, method.NameAndSignature, method.Instantiation);
290304
}
291305

292306
// If we cannot find an exact method entry point, look for an equivalent template and compute the generic dictionary
@@ -371,11 +385,12 @@ private static bool TryGetStaticGenericMethodDictionary(GenericMethodLookupData
371385
return false;
372386
}
373387

374-
private bool TryGetDynamicGenericMethodComponents(IntPtr methodDictionary, out RuntimeTypeHandle declaringType, out MethodNameAndSignature methodNameAndSignature, out RuntimeTypeHandle[] genericMethodArgumentHandles)
388+
private bool TryGetDynamicGenericMethodComponents(IntPtr methodDictionary, out RuntimeTypeHandle declaringType, out MethodNameAndSignature methodNameAndSignature, out RuntimeTypeHandle[] genericMethodArgumentHandles, out bool isAsyncVariant)
375389
{
376390
declaringType = default(RuntimeTypeHandle);
377391
methodNameAndSignature = null;
378392
genericMethodArgumentHandles = null;
393+
isAsyncVariant = false;
379394

380395
using (_dynamicGenericsLock.EnterScope())
381396
{
@@ -389,10 +404,12 @@ private bool TryGetDynamicGenericMethodComponents(IntPtr methodDictionary, out R
389404
declaringType = entry._declaringTypeHandle;
390405
methodNameAndSignature = entry._methodNameAndSignature;
391406
genericMethodArgumentHandles = entry._genericMethodArgumentHandles;
407+
isAsyncVariant = entry._isAsyncVariant;
392408
return true;
393409
}
394410
}
395-
private static unsafe bool TryGetStaticGenericMethodComponents(IntPtr methodDictionary, out RuntimeTypeHandle declaringType, out MethodNameAndSignature nameAndSignature, out RuntimeTypeHandle[] genericMethodArgumentHandles)
411+
412+
private static unsafe bool TryGetStaticGenericMethodComponents(IntPtr methodDictionary, out RuntimeTypeHandle declaringType, out MethodNameAndSignature nameAndSignature, out RuntimeTypeHandle[] genericMethodArgumentHandles, out bool isAsyncVariant)
396413
{
397414
// Generic method dictionaries have a header that has the hash code in it. Locate the header
398415
IntPtr dictionaryHeader = IntPtr.Subtract(methodDictionary, IntPtr.Size);
@@ -420,7 +437,12 @@ private static unsafe bool TryGetStaticGenericMethodComponents(IntPtr methodDict
420437
// We have a match - fill in the results
421438
declaringType = externalReferencesLookup.GetRuntimeTypeHandleFromIndex(entryParser.GetUnsigned());
422439

423-
int token = (int)entryParser.GetUnsigned();
440+
441+
int flagsAndToken = (int)entryParser.GetUnsigned();
442+
isAsyncVariant = (flagsAndToken & GenericMethodsHashtableConstants.IsAsyncVariant) != 0;
443+
444+
int token = ((int)HandleType.Method << 25) | (flagsAndToken & ~GenericMethodsHashtableConstants.IsAsyncVariant);
445+
424446
nameAndSignature = new MethodNameAndSignature(module.MetadataReader, token.AsHandle().ToMethodHandle(module.MetadataReader));
425447

426448
uint arity = entryParser.GetSequenceCount();
@@ -438,6 +460,7 @@ private static unsafe bool TryGetStaticGenericMethodComponents(IntPtr methodDict
438460
declaringType = default(RuntimeTypeHandle);
439461
nameAndSignature = null;
440462
genericMethodArgumentHandles = null;
463+
isAsyncVariant = false;
441464
return false;
442465
}
443466

src/coreclr/nativeaot/System.Private.TypeLoader/src/Internal/Runtime/TypeLoader/TypeLoaderEnvironment.GVMResolution.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -311,7 +311,7 @@ private static InstantiatedMethod FindMatchingInterfaceSlot(NativeFormatModuleIn
311311
Debug.Assert(interfaceImplType != null);
312312
}
313313

314-
return (InstantiatedMethod)context.ResolveGenericMethodInstantiation(false, interfaceImplType, targetMethodNameAndSignature, slotMethod.Instantiation);
314+
return (InstantiatedMethod)context.ResolveGenericMethodInstantiation(false, slotMethod.AsyncVariant, interfaceImplType, targetMethodNameAndSignature, slotMethod.Instantiation);
315315
}
316316
}
317317
}
@@ -502,7 +502,7 @@ private static InstantiatedMethod ResolveGenericVirtualMethodTarget(DefType targ
502502
Debug.Assert(targetMethodNameAndSignature != null);
503503

504504
TypeSystemContext context = slotMethod.Context;
505-
return (InstantiatedMethod)context.ResolveGenericMethodInstantiation(false, targetType, targetMethodNameAndSignature, slotMethod.Instantiation);
505+
return (InstantiatedMethod)context.ResolveGenericMethodInstantiation(false, slotMethod.AsyncVariant, targetType, targetMethodNameAndSignature, slotMethod.Instantiation);
506506
}
507507
}
508508

0 commit comments

Comments
 (0)