diff --git a/eng/pipelines/coreclr/crossgen2.yml b/eng/pipelines/coreclr/crossgen2.yml
index 775928f7d3ffc2..4d755a1823a800 100644
--- a/eng/pipelines/coreclr/crossgen2.yml
+++ b/eng/pipelines/coreclr/crossgen2.yml
@@ -99,6 +99,30 @@ extends:
creator: dotnet-bot
testRunNamePrefixSuffix: TestReadyToRun_$(_BuildConfig)
+ - template: /eng/pipelines/common/platform-matrix.yml
+ parameters:
+ jobTemplate: /eng/pipelines/common/global-build-job.yml
+ helixQueuesTemplate: /eng/pipelines/libraries/helix-queues-setup.yml
+ buildConfig: Release
+ platforms:
+ - linux_x64
+ - windows_x64
+ jobParameters:
+ testGroup: innerloop
+ buildArgs: -s clr+libs+libs.tests
+ -c $(_BuildConfig)
+ /p:TestReadyToRun=true
+ /p:TestRuntimeAsync=true
+ /p:UseRuntimeAsync=true
+ /p:ArchiveTests=true
+ nameSuffix: TestReadyToRun_RuntimeAsync_Libraries
+ timeoutInMinutes: 360
+ postBuildSteps:
+ - template: /eng/pipelines/libraries/helix.yml
+ parameters:
+ creator: dotnet-bot
+ testRunNamePrefixSuffix: TestReadyToRun_RuntimeAsync_$(_BuildConfig)
+
# Run pri0 tests with hot/cold splitting enabled (only supported on x64 at the moment)
# TODO: test on arm64 once supported
- template: /eng/pipelines/common/platform-matrix.yml
diff --git a/eng/testing/tests.targets b/eng/testing/tests.targets
index 6579ff2f0ba884..f7d324120d6f04 100644
--- a/eng/testing/tests.targets
+++ b/eng/testing/tests.targets
@@ -2,7 +2,6 @@
+ true
+ $(Features);runtime-async=on
+
true
false
diff --git a/src/coreclr/inc/corcompile.h b/src/coreclr/inc/corcompile.h
index 16b688eaa57e2e..8e8c78c70b4fcf 100644
--- a/src/coreclr/inc/corcompile.h
+++ b/src/coreclr/inc/corcompile.h
@@ -110,6 +110,7 @@ enum EncodeMethodSigFlags
ENCODE_METHOD_SIG_OwnerType = 0x40,
ENCODE_METHOD_SIG_UpdateContext = 0x80,
ENCODE_METHOD_SIG_AsyncVariant = 0x100,
+ ENCODE_METHOD_SIG_ResumptionStub = 0x200,
};
enum EncodeFieldSigFlags
diff --git a/src/coreclr/inc/readytorun.h b/src/coreclr/inc/readytorun.h
index 7956fa7b77d0cb..6fb4987a06ef2a 100644
--- a/src/coreclr/inc/readytorun.h
+++ b/src/coreclr/inc/readytorun.h
@@ -468,9 +468,10 @@ enum ReadyToRunHelper
READYTORUN_HELPER_GetCurrentManagedThreadId = 0x112,
- READYTORUN_HELPER_AllocContinuation = 0x113,
- READYTORUN_HELPER_AllocContinuationClass = 0x114,
- READYTORUN_HELPER_AllocContinuationMethod = 0x115,
+ // Async continuation helpers
+ READYTORUN_HELPER_AllocContinuation = 0x113,
+ READYTORUN_HELPER_AllocContinuationClass = 0x114,
+ READYTORUN_HELPER_AllocContinuationMethod = 0x115,
};
#include "readytoruninstructionset.h"
diff --git a/src/coreclr/inc/readytorunhelpers.h b/src/coreclr/inc/readytorunhelpers.h
index f2b768aad86db8..730cff8ba8c645 100644
--- a/src/coreclr/inc/readytorunhelpers.h
+++ b/src/coreclr/inc/readytorunhelpers.h
@@ -20,6 +20,7 @@ HELPER(READYTORUN_HELPER_RngChkFail, CORINFO_HELP_RNGCHKFAIL,
HELPER(READYTORUN_HELPER_FailFast, CORINFO_HELP_FAIL_FAST, OPTIMIZEFORSIZE)
HELPER(READYTORUN_HELPER_ThrowNullRef, CORINFO_HELP_THROWNULLREF, OPTIMIZEFORSIZE)
HELPER(READYTORUN_HELPER_ThrowDivZero, CORINFO_HELP_THROWDIVZERO, OPTIMIZEFORSIZE)
+HELPER(READYTORUN_HELPER_ThrowExact, CORINFO_HELP_THROWEXACT, OPTIMIZEFORSIZE)
HELPER(READYTORUN_HELPER_WriteBarrier, CORINFO_HELP_ASSIGN_REF, )
HELPER(READYTORUN_HELPER_CheckedWriteBarrier, CORINFO_HELP_CHECKED_ASSIGN_REF, )
diff --git a/src/coreclr/tools/Common/Compiler/AsyncMethodVariant.cs b/src/coreclr/tools/Common/Compiler/AsyncMethodVariant.cs
index f27466aaefa469..6b7d0f39a0c3d2 100644
--- a/src/coreclr/tools/Common/Compiler/AsyncMethodVariant.cs
+++ b/src/coreclr/tools/Common/Compiler/AsyncMethodVariant.cs
@@ -81,7 +81,7 @@ public static bool IsAsyncVariant(this MethodDesc method)
public static bool IsAsyncThunk(this MethodDesc method)
{
- return method.IsAsyncVariant() ^ method.IsAsync;
+ return (method.IsAsyncVariant() ^ method.IsAsync) || method is AsyncResumptionStub;
}
public static MethodDesc GetAsyncVariant(this MethodDesc method)
diff --git a/src/coreclr/tools/Common/Compiler/CompilerTypeSystemContext.Async.cs b/src/coreclr/tools/Common/Compiler/CompilerTypeSystemContext.Async.cs
index a8c6f1b357a352..b7982e6d484059 100644
--- a/src/coreclr/tools/Common/Compiler/CompilerTypeSystemContext.Async.cs
+++ b/src/coreclr/tools/Common/Compiler/CompilerTypeSystemContext.Async.cs
@@ -203,6 +203,7 @@ protected override bool CompareValueToValue(AsyncMethodVariant value1, AsyncMeth
public MetadataType GetContinuationType(GCPointerMap pointerMap)
{
var cont = _continuationTypeHashtable.GetOrCreateValue(pointerMap);
+ _validTypes.TryAdd(cont);
return cont;
}
diff --git a/src/coreclr/tools/Common/Internal/Runtime/ReadyToRunConstants.cs b/src/coreclr/tools/Common/Internal/Runtime/ReadyToRunConstants.cs
index f678e38bb1cf0b..a1b709e7e0f0ca 100644
--- a/src/coreclr/tools/Common/Internal/Runtime/ReadyToRunConstants.cs
+++ b/src/coreclr/tools/Common/Internal/Runtime/ReadyToRunConstants.cs
@@ -54,6 +54,7 @@ public enum ReadyToRunMethodSigFlags : uint
READYTORUN_METHOD_SIG_OwnerType = 0x40,
READYTORUN_METHOD_SIG_UpdateContext = 0x80,
READYTORUN_METHOD_SIG_AsyncVariant = 0x100,
+ READYTORUN_METHOD_SIG_ResumptionStub = 0x200,
}
[Flags]
diff --git a/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs b/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs
index ec4cc5a86eb1d8..bbaf6526398958 100644
--- a/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs
+++ b/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs
@@ -27,6 +27,7 @@
using ILCompiler.DependencyAnalysis;
#if READYTORUN
+using ILCompiler.ReadyToRun.TypeSystem;
using System.Reflection.Metadata.Ecma335;
using ILCompiler.DependencyAnalysis.ReadyToRun;
#endif
@@ -118,7 +119,7 @@ public LikelyClassMethodRecord(IntPtr handle, uint likelihood)
private static extern uint getLikelyClasses(LikelyClassMethodRecord* pLikelyClasses, uint maxLikelyClasses, PgoInstrumentationSchema* schema, uint countSchemaItems, byte*pInstrumentationData, int ilOffset);
[DllImport(JitLibrary)]
- private static extern uint getLikelyMethods(LikelyClassMethodRecord* pLikelyMethods, uint maxLikelyMethods, PgoInstrumentationSchema* schema, uint countSchemaItems, byte*pInstrumentationData, int ilOffset);
+ private static extern uint getLikelyMethods(LikelyClassMethodRecord* pLikelyMethods, uint maxLikelyMethods, PgoInstrumentationSchema* schema, uint countSchemaItems, byte* pInstrumentationData, int ilOffset);
[DllImport(JitSupportLibrary)]
private static extern IntPtr GetJitHost(IntPtr configProvider);
@@ -1382,8 +1383,10 @@ private bool resolveVirtualMethod(CORINFO_DEVIRTUALIZATION_INFO* info)
// a safe condition, and we could delete this assert. This assert exists in order to help identify
// cases where the virtual function resolution algorithm either does not function, or is not used
// correctly.
+ // TODO: Async variant devirtualization algorithm
#if DEBUG
- if (info->detail == CORINFO_DEVIRTUALIZATION_DETAIL.CORINFO_DEVIRTUALIZATION_UNKNOWN)
+ if (info->detail == CORINFO_DEVIRTUALIZATION_DETAIL.CORINFO_DEVIRTUALIZATION_UNKNOWN
+ && !decl.IsAsyncVariant())
{
Console.Error.WriteLine($"Failed devirtualization with unexpected unknown failure while compiling {MethodBeingCompiled} with decl {decl} targeting type {objType}");
Debug.Assert(info->detail != CORINFO_DEVIRTUALIZATION_DETAIL.CORINFO_DEVIRTUALIZATION_UNKNOWN);
@@ -3803,7 +3806,11 @@ private bool getTailCallHelpers(ref CORINFO_RESOLVED_TOKEN callToken, CORINFO_SI
#pragma warning restore CA1822 // Mark members as static
{
#if READYTORUN
- throw new NotImplementedException("Crossgen2 does not support runtime-async yet");
+ var resumptionStub = new AsyncResumptionStub(MethodBeingCompiled, MethodBeingCompiled.OwningType);
+
+ // CompiledMethodNode instead of MethodEntrypoint for the pointer to the code instead of a fixup
+ entryPoint = (void*)ObjectToHandle(_compilation.NodeFactory.CompiledMethodNode(resumptionStub));
+ return ObjectToHandle(resumptionStub);
#else
_asyncResumptionStub ??= new AsyncResumptionStub(MethodBeingCompiled, _compilation.TypeSystemContext.GeneratedAssembly.GetGlobalModuleType());
diff --git a/src/coreclr/tools/Common/TypeSystem/IL/InstantiatedMethodIL.cs b/src/coreclr/tools/Common/TypeSystem/IL/InstantiatedMethodIL.cs
index 5d4fb39c953a11..d2da424faa21af 100644
--- a/src/coreclr/tools/Common/TypeSystem/IL/InstantiatedMethodIL.cs
+++ b/src/coreclr/tools/Common/TypeSystem/IL/InstantiatedMethodIL.cs
@@ -18,7 +18,6 @@ public InstantiatedMethodIL(MethodDesc owningMethod, MethodIL methodIL)
{
Debug.Assert(methodIL.GetMethodILDefinition() == methodIL);
Debug.Assert(owningMethod.HasInstantiation || owningMethod.OwningType.HasInstantiation);
- Debug.Assert(owningMethod.GetTypicalMethodDefinition() == methodIL.OwningMethod);
_methodIL = methodIL;
_method = owningMethod;
diff --git a/src/coreclr/tools/Common/TypeSystem/IL/Stubs/AsyncResumptionStub.cs b/src/coreclr/tools/Common/TypeSystem/IL/Stubs/AsyncResumptionStub.cs
index 16154f87c97d25..dbff48e25c7f44 100644
--- a/src/coreclr/tools/Common/TypeSystem/IL/Stubs/AsyncResumptionStub.cs
+++ b/src/coreclr/tools/Common/TypeSystem/IL/Stubs/AsyncResumptionStub.cs
@@ -2,7 +2,6 @@
// The .NET Foundation licenses this file to you under the MIT license.
using System;
-
using Internal.IL;
using Internal.IL.Stubs;
using Internal.TypeSystem;
@@ -26,7 +25,7 @@ public AsyncResumptionStub(MethodDesc targetMethod, TypeDesc owningType)
}
public override ReadOnlySpan Name => _targetMethod.Name;
- public override string DiagnosticName => _targetMethod.DiagnosticName;
+ public override string DiagnosticName => "RESUME_" + _targetMethod.DiagnosticName;
public override TypeDesc OwningType => _owningType;
@@ -36,6 +35,12 @@ public AsyncResumptionStub(MethodDesc targetMethod, TypeDesc owningType)
public MethodDesc TargetMethod => _targetMethod;
+ ///
+ /// The hash of the async variant method is used at runtime to find the bucket of the resumption stub.
+ /// These should be identical for the async variant and the resumption stub.
+ ///
+ protected override int ComputeHashCode() => _targetMethod.GetHashCode();
+
private MethodSignature InitializeSignature()
{
TypeDesc objectType = Context.GetWellKnownType(WellKnownType.Object);
@@ -109,6 +114,7 @@ public override MethodIL EmitIL()
}
ilStream.EmitLdLoc(newContinuationLocal);
ilStream.Emit(ILOpcode.ret);
+ ilEmitter.SetHasGeneratedTokens();
return ilEmitter.Link(this);
}
diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/ExceptionInfoLookupTableNode.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/ExceptionInfoLookupTableNode.cs
index f9fe3657851a16..e5c3a71595c810 100644
--- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/ExceptionInfoLookupTableNode.cs
+++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/ExceptionInfoLookupTableNode.cs
@@ -94,6 +94,10 @@ internal void LayoutMethodsWithEHInfo()
foreach (MethodWithGCInfo method in _nodeFactory.EnumerateCompiledMethods())
{
+ if (method.Method is AsyncResumptionStub)
+ {
+ continue;
+ }
ObjectData ehInfo = method.EHInfo;
if (ehInfo != null && ehInfo.Data.Length != 0)
{
diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/InliningInfoNode.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/InliningInfoNode.cs
index d28bd07e04921c..2df6065fb4a8f1 100644
--- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/InliningInfoNode.cs
+++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/InliningInfoNode.cs
@@ -6,7 +6,7 @@
using System.Diagnostics;
using System.IO;
using System.Reflection.Metadata.Ecma335;
-
+using ILCompiler.ReadyToRun.TypeSystem;
using Internal;
using Internal.NativeFormat;
using Internal.ReadyToRunConstants;
@@ -67,8 +67,17 @@ public override ObjectData GetData(NodeFactory factory, bool relocsOnly = false)
foreach (MethodWithGCInfo methodNode in factory.EnumerateCompiledMethods(_module, CompiledMethodCategory.All))
{
MethodDesc[] inlinees = methodNode.InlinedMethods;
+ if (inlinees.Length == 0)
+ {
+ continue;
+ }
MethodDesc inliner = methodNode.Method;
- EcmaMethod inlinerDefinition = (EcmaMethod)inliner.GetTypicalMethodDefinition();
+ if (inliner.IsAsyncThunk())
+ {
+ // Async thunks are generated by crossgen and diagnostic tools don't need to worry about them
+ continue;
+ }
+ EcmaMethod inlinerDefinition = (EcmaMethod)inliner.GetPrimaryMethodDesc().GetTypicalMethodDefinition();
if (inlinerDefinition.IsNonVersionable())
{
diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/InstanceEntryPointTableNode.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/InstanceEntryPointTableNode.cs
index db63a30fae67a5..4ea76908662708 100644
--- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/InstanceEntryPointTableNode.cs
+++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/InstanceEntryPointTableNode.cs
@@ -7,7 +7,7 @@
using System.IO;
using System.Linq;
using System.Reflection.Metadata.Ecma335;
-
+using ILCompiler.ReadyToRun.TypeSystem;
using Internal;
using Internal.JitInterface;
using Internal.NativeFormat;
@@ -53,17 +53,17 @@ public override void AppendMangledName(NameMangler nameMangler, Utf8StringBuilde
public static byte[] BuildSignatureForMethodDefinedInModule(MethodDesc method, NodeFactory factory)
{
- EcmaMethod typicalMethod = (EcmaMethod)method.GetTypicalMethodDefinition();
+ EcmaMethod ecmaMethod = (EcmaMethod)method.GetPrimaryMethodDesc().GetTypicalMethodDefinition();
ModuleToken moduleToken;
- if (factory.CompilationModuleGroup.VersionsWithMethodBody(typicalMethod))
+ if (factory.CompilationModuleGroup.VersionsWithMethodBody(ecmaMethod))
{
- moduleToken = new ModuleToken(typicalMethod.Module, typicalMethod.Handle);
+ moduleToken = new ModuleToken(ecmaMethod.Module, ecmaMethod.Handle);
}
else
{
MutableModule manifestMetadata = factory.ManifestMetadataTable._mutableModule;
- var handle = manifestMetadata.TryGetExistingEntityHandle(method.GetTypicalMethodDefinition());
+ var handle = manifestMetadata.TryGetExistingEntityHandle(ecmaMethod);
Debug.Assert(handle.HasValue);
moduleToken = new ModuleToken(factory.ManifestMetadataTable._mutableModule, handle.Value);
}
@@ -102,7 +102,7 @@ public override ObjectData GetData(NodeFactory factory, bool relocsOnly = false)
foreach (MethodWithGCInfo method in factory.EnumerateCompiledMethods(null, CompiledMethodCategory.Instantiated))
{
- Debug.Assert(method.Method.HasInstantiation || method.Method.OwningType.HasInstantiation);
+ Debug.Assert(method.Method.HasInstantiation || method.Method.OwningType.HasInstantiation || method.Method.IsAsyncVariant() || method.Method is AsyncResumptionStub);
int methodIndex = factory.RuntimeFunctionsTable.GetIndex(method);
diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/MethodFixupSignature.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/MethodFixupSignature.cs
index e117bb2fb3be87..89a91aa6298f36 100644
--- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/MethodFixupSignature.cs
+++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/MethodFixupSignature.cs
@@ -113,7 +113,7 @@ public override ObjectData GetData(NodeFactory factory, bool relocsOnly = false)
MethodWithToken method = _method;
- if (factory.CompilationModuleGroup.VersionsWithMethodBody(method.Method) && !method.Method.IsAsyncVariant())
+ if (factory.CompilationModuleGroup.VersionsWithMethodBody(method.Method) && !method.Method.IsAsyncVariant() && method.Method is not AsyncResumptionStub)
{
if (method.Token.TokenType == CorTokenType.mdtMethodSpec)
{
@@ -130,6 +130,7 @@ public override ObjectData GetData(NodeFactory factory, bool relocsOnly = false)
SignatureContext innerContext = dataBuilder.EmitFixup(factory, fixupKind, method.Token.Module, factory.SignatureContext);
+ // We should emit AsyncVariants and ResumptionStubs differently even if the ModuleToken is a Def or Ref
if (optimized && method.Token.TokenType == CorTokenType.mdtMethodDef)
{
dataBuilder.EmitMethodDefToken(method.Token);
diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/ModuleTokenResolver.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/ModuleTokenResolver.cs
index 2f352b21a4fe7b..2a0e2a10c15c30 100644
--- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/ModuleTokenResolver.cs
+++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/ModuleTokenResolver.cs
@@ -12,6 +12,7 @@
using Internal.TypeSystem.Ecma;
using Internal.CorConstants;
using System.Diagnostics;
+using ILCompiler.ReadyToRun.TypeSystem;
namespace ILCompiler.DependencyAnalysis.ReadyToRun
{
@@ -61,7 +62,6 @@ public ModuleToken GetModuleTokenForType(TypeDesc type, bool allowDynamicallyCre
{
return new ModuleToken(ecmaType.Module, (mdToken)MetadataTokens.GetToken(ecmaType.Handle));
}
-
if (_typeToRefTokens.TryGetValue(ecmaType, out token))
{
return token;
@@ -100,7 +100,7 @@ public ModuleToken GetModuleTokenForMethod(MethodDesc method, bool allowDynamica
{
method = method.GetCanonMethodTarget(CanonicalFormKind.Specific);
- if (method.GetTypicalMethodDefinition() is EcmaMethod ecmaMethod)
+ if (method.GetPrimaryMethodDesc().GetTypicalMethodDefinition() is EcmaMethod ecmaMethod)
{
if (_compilationModuleGroup.VersionsWithMethodBody(ecmaMethod))
{
@@ -129,6 +129,31 @@ public ModuleToken GetModuleTokenForMethod(MethodDesc method, bool allowDynamica
}
}
+ public void AddModuleTokenForMethod(MethodDesc method, ModuleToken token)
+ {
+ if (token.TokenType == CorTokenType.mdtMethodSpec)
+ {
+ MethodSpecification methodSpec = token.MetadataReader.GetMethodSpecification((MethodSpecificationHandle)token.Handle);
+ DecodeMethodSpecificationSignatureToDiscoverUsedTypeTokens(methodSpec.Signature, token);
+ token = new ModuleToken(token.Module, methodSpec.Method);
+ }
+
+ if (token.TokenType == CorTokenType.mdtMemberRef)
+ {
+ MemberReference memberRef = token.MetadataReader.GetMemberReference((MemberReferenceHandle)token.Handle);
+ EntityHandle owningTypeHandle = memberRef.Parent;
+ TypeDesc owningType = (TypeDesc)token.Module.GetObject(owningTypeHandle, NotFoundBehavior.Throw);
+ AddModuleTokenForType(owningType, new ModuleToken(token.Module, owningTypeHandle));
+ DecodeMethodSignatureToDiscoverUsedTypeTokens(memberRef.Signature, token);
+ }
+ if (token.TokenType == CorTokenType.mdtMethodDef)
+ {
+ MethodDefinition methodDef = token.MetadataReader.GetMethodDefinition((MethodDefinitionHandle)token.Handle);
+ TokenResolverProvider rentedProvider = TokenResolverProvider.Rent(this, token.Module);
+ DecodeMethodSignatureToDiscoverUsedTypeTokens(methodDef.Signature, token);
+ }
+ }
+
public ModuleToken GetModuleTokenForField(FieldDesc field, bool allowDynamicallyCreatedReference, bool throwIfNotFound)
{
if (field.GetTypicalFieldDefinition() is EcmaField ecmaField)
@@ -160,32 +185,6 @@ public ModuleToken GetModuleTokenForField(FieldDesc field, bool allowDynamically
}
}
-
- public void AddModuleTokenForMethod(MethodDesc method, ModuleToken token)
- {
- if (token.TokenType == CorTokenType.mdtMethodSpec)
- {
- MethodSpecification methodSpec = token.MetadataReader.GetMethodSpecification((MethodSpecificationHandle)token.Handle);
- DecodeMethodSpecificationSignatureToDiscoverUsedTypeTokens(methodSpec.Signature, token);
- token = new ModuleToken(token.Module, methodSpec.Method);
- }
-
- if (token.TokenType == CorTokenType.mdtMemberRef)
- {
- MemberReference memberRef = token.MetadataReader.GetMemberReference((MemberReferenceHandle)token.Handle);
- EntityHandle owningTypeHandle = memberRef.Parent;
- TypeDesc owningType = (TypeDesc)token.Module.GetObject(owningTypeHandle, NotFoundBehavior.Throw);
- AddModuleTokenForType(owningType, new ModuleToken(token.Module, owningTypeHandle));
- DecodeMethodSignatureToDiscoverUsedTypeTokens(memberRef.Signature, token);
- }
- if (token.TokenType == CorTokenType.mdtMethodDef)
- {
- MethodDefinition methodDef = token.MetadataReader.GetMethodDefinition((MethodDefinitionHandle)token.Handle);
- TokenResolverProvider rentedProvider = TokenResolverProvider.Rent(this, token.Module);
- DecodeMethodSignatureToDiscoverUsedTypeTokens(methodDef.Signature, token);
- }
- }
-
private void DecodeMethodSpecificationSignatureToDiscoverUsedTypeTokens(BlobHandle signatureHandle, ModuleToken token)
{
MetadataReader metadataReader = token.MetadataReader;
@@ -332,6 +331,10 @@ public void AddModuleTokenForType(TypeDesc type, ModuleToken token)
SetModuleTokenForTypeSystemEntity(_typeToRefTokens, ecmaType, token);
}
}
+ else if (type.IsCanonicalDefinitionType(CanonicalFormKind.Specific))
+ {
+ return;
+ }
else if (!specialTypeFound)
{
throw new NotImplementedException(type.ToString());
diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/SignatureBuilder.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/SignatureBuilder.cs
index dd16a969b26297..8c5738a32018dc 100644
--- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/SignatureBuilder.cs
+++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/SignatureBuilder.cs
@@ -445,6 +445,13 @@ public void EmitMethodSignature(
{
flags |= (uint)ReadyToRunMethodSigFlags.READYTORUN_METHOD_SIG_AsyncVariant;
}
+ if (method.Method is AsyncResumptionStub)
+ {
+ flags |= (uint)ReadyToRunMethodSigFlags.READYTORUN_METHOD_SIG_ResumptionStub;
+ // For AsyncResumptionStubs, we want to encode the signature of the async variant method, not the resumption stub itself,
+ // so that they will share the same hash and be placed in the same bucket at runtime
+ method = new MethodWithToken(((AsyncResumptionStub)method.Method).TargetMethod, method.Token, method.ConstrainedType, method.Unboxing, method.Method);
+ }
EmitMethodSpecificationSignature(method, flags, enforceDefEncoding, enforceOwningType, context);
@@ -615,7 +622,7 @@ public SignatureContext EmitFixup(NodeFactory factory, ReadyToRunFixupKind fixup
{
throw new InternalCompilerErrorException("Attempt to use token from a module not within the version bubble");
}
-
+
EmitUInt((uint)factory.ManifestMetadataTable.ModuleToIndex(targetModule));
return new SignatureContext(targetModule, outerContext.Resolver);
}
diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/TypeFixupSignature.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/TypeFixupSignature.cs
index 9ef8a79d801cc7..540a3c722ac036 100644
--- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/TypeFixupSignature.cs
+++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/TypeFixupSignature.cs
@@ -72,7 +72,6 @@ public override ObjectData GetData(NodeFactory factory, bool relocsOnly = false)
private static void EncodeTypeLayout(ObjectDataSignatureBuilder dataBuilder, TypeDesc type)
{
- Debug.Assert(type.IsValueType);
MetadataType defType = (MetadataType)type;
int pointerSize = type.Context.Target.PointerSize;
@@ -121,7 +120,7 @@ private static void EncodeTypeLayout(ObjectDataSignatureBuilder dataBuilder, Typ
// Encode the GC pointer map
GCPointerMap gcMap = GCPointerMap.FromInstanceLayout(defType);
- byte[] encodedGCRefMap = new byte[(size / pointerSize + 7) / 8];
+ byte[] encodedGCRefMap = new byte[((size + (pointerSize - 1)) / pointerSize + 7) / 8];
int bitIndex = 0;
foreach (bool bit in gcMap)
{
diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRunSymbolNodeFactory.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRunSymbolNodeFactory.cs
index 981ae21d19507e..9e2cb3a08d01a2 100644
--- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRunSymbolNodeFactory.cs
+++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRunSymbolNodeFactory.cs
@@ -413,7 +413,7 @@ private ISymbolNode CreateMethodDictionary(MethodWithToken method)
return new PrecodeHelperImport(
_codegenNodeFactory,
_codegenNodeFactory.MethodSignature(
- ReadyToRunFixupKind.MethodDictionary,
+ ReadyToRunFixupKind.MethodDictionary,
method,
isInstantiatingStub: true));
}
diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunCodegenCompilation.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunCodegenCompilation.cs
index 122682028f3353..9d69e66e4adad7 100644
--- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunCodegenCompilation.cs
+++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunCodegenCompilation.cs
@@ -109,9 +109,8 @@ public bool CanInline(MethodDesc caller, MethodDesc callee)
}
}
- if (callee.IsAsyncThunk())
+ if (callee.IsAsyncThunk() || callee.IsAsyncCall())
{
- // Async thunks require special handling in the compiler and should not be inlined
return false;
}
@@ -302,7 +301,7 @@ public sealed class ReadyToRunCodegenCompilation : Compilation
private readonly ProfileDataManager _profileData;
private readonly FileLayoutOptimizer _fileLayoutOptimizer;
- private readonly HashSet _methodsWhichNeedMutableILBodies = new HashSet();
+ private readonly HashSet _methodsWhichNeedMutableILBodies = new HashSet();
private readonly HashSet _methodsToRecompile = new HashSet();
public ProfileDataManager ProfileData => _profileData;
@@ -695,12 +694,15 @@ protected override void ComputeDependencyNodeDependencies(List comparison = (EcmaMethod a, EcmaMethod b) => comparer.Compare(a, b);
+ Comparison comparison = (MethodDesc a, MethodDesc b) => comparer.Compare(a, b);
Array.Sort(mutableMethodBodyNeedList, comparison);
var ilProvider = (ReadyToRunILProvider)_methodILCache.ILProvider;
diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunTableManager.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunTableManager.cs
index 25bec37f74584a..b9b86646d30aa9 100644
--- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunTableManager.cs
+++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunTableManager.cs
@@ -79,13 +79,13 @@ protected virtual void Graph_NewMarkedNode(DependencyNodeCore obj)
{
Debug.Assert(!_sortedMethods);
MethodDesc method = methodNode.Method;
- EcmaModule module = (EcmaModule)((EcmaMethod)method.GetTypicalMethodDefinition().GetPrimaryMethodDesc()).Module;
+ EcmaModule module = (EcmaModule)((EcmaMethod)method.GetPrimaryMethodDesc().GetTypicalMethodDefinition().GetTypicalMethodDefinition()).Module;
if (!_methodsGenerated.TryGetValue(module, out var perModuleData))
{
perModuleData = new PerModuleMethodsGenerated(module);
_methodsGenerated[module] = perModuleData;
}
- if (method.HasInstantiation || method.OwningType.HasInstantiation)
+ if (method.HasInstantiation || method.OwningType.HasInstantiation || method.IsAsyncVariant() || method is AsyncResumptionStub)
{
perModuleData.GenericMethodsGenerated.Add(methodNode);
}
diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/IL/ReadyToRunILProvider.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/IL/ReadyToRunILProvider.cs
index bbfcc01d500442..aa5d8c413e036c 100644
--- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/IL/ReadyToRunILProvider.cs
+++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/IL/ReadyToRunILProvider.cs
@@ -14,6 +14,7 @@
using System.Buffers.Binary;
using System.Reflection.Metadata;
using System.Reflection.Metadata.Ecma335;
+using ILCompiler.ReadyToRun.TypeSystem;
namespace Internal.IL
{
@@ -122,18 +123,48 @@ private MethodIL TryGetPerInstantiationIntrinsicMethodIL(MethodDesc method)
return null;
}
- private Dictionary _manifestModuleWrappedMethods = new Dictionary();
+ private Dictionary _manifestModuleWrappedMethods = new Dictionary();
// Create the cross module inlineable tokens for a method
// This method is order dependent, and must be called during the single threaded portion of compilation
- public void CreateCrossModuleInlineableTokensForILBody(EcmaMethod method)
+ public void CreateCrossModuleInlineableTokensForILBody(MethodDesc method)
{
+ // This method accepts only method definitions, but accepts non-primary method definitions.
+ // That is, it must not be generic, but it may represent an AsyncVariant
+ Debug.Assert(method.IsTypicalMethodDefinition);
Debug.Assert(_manifestMutableModule != null);
var wrappedMethodIL = new ManifestModuleWrappedMethodIL();
if (method.IsAsync)
{
- if (!wrappedMethodIL.Initialize(_manifestMutableModule, GetMethodILForAsyncMethod(method), method, false))
+ if (!wrappedMethodIL.Initialize(_manifestMutableModule, GetMethodILForAsyncMethod(method), (EcmaMethod)method, false))
+ {
+ // If we could not initialize the wrapped method IL, we should store a null.
+ // That will result in the IL code for the method being unavailable for use in
+ // the compilation, which is version safe.
+ wrappedMethodIL = null;
+ }
+ }
+ else if (method.IsAsyncVariant())
+ {
+ if (!wrappedMethodIL.Initialize(_manifestMutableModule,
+ AsyncThunkILEmitter.EmitAsyncMethodThunk(method, method.GetTargetOfAsyncVariant()),
+ method,
+ false))
+ {
+ // If we could not initialize the wrapped method IL, we should store a null.
+ // That will result in the IL code for the method being unavailable for use in
+ // the compilation, which is version safe.
+ wrappedMethodIL = null;
+ }
+ }
+ else if (method is AsyncResumptionStub ars)
+ {
+ if (!wrappedMethodIL.Initialize(
+ _manifestMutableModule,
+ ars.EmitIL(),
+ ars,
+ false))
{
// If we could not initialize the wrapped method IL, we should store a null.
// That will result in the IL code for the method being unavailable for use in
@@ -146,7 +177,7 @@ public void CreateCrossModuleInlineableTokensForILBody(EcmaMethod method)
Debug.Assert(!_compilationModuleGroup.VersionsWithMethodBody(method) &&
_compilationModuleGroup.CrossModuleInlineable(method));
- if (!wrappedMethodIL.Initialize(_manifestMutableModule, EcmaMethodIL.Create(method)))
+ if (!wrappedMethodIL.Initialize(_manifestMutableModule, EcmaMethodIL.Create((EcmaMethod)method)))
{
// If we could not initialize the wrapped method IL, we should store a null.
// That will result in the IL code for the method being unavailable for use in
@@ -159,38 +190,48 @@ public void CreateCrossModuleInlineableTokensForILBody(EcmaMethod method)
IncrementVersion();
}
- public bool NeedsCrossModuleInlineableTokens(EcmaMethod method)
+ public bool NeedsCrossModuleInlineableTokens(MethodDesc method)
{
- if (((!_compilationModuleGroup.VersionsWithMethodBody(method) &&
- _compilationModuleGroup.CrossModuleInlineable(method))
- || NeedsTaskReturningThunk(method))
- && !_manifestModuleWrappedMethods.ContainsKey(method))
+ if (((!_compilationModuleGroup.VersionsWithMethodBody(method)
+ && _compilationModuleGroup.CrossModuleInlineable(method))
+ || (NeedsTaskReturningThunk(method) || NeedsAsyncThunk(method) || method is AsyncResumptionStub))
+ && !_manifestModuleWrappedMethods.ContainsKey(method))
{
return true;
}
return false;
}
- bool NeedsTaskReturningThunk(EcmaMethod method)
+ bool NeedsTaskReturningThunk(MethodDesc method)
{
+ if (method is not EcmaMethod ecmaMethod)
+ return false;
+
if (!method.IsAsync)
return false;
if (method.Signature.ReturnsTaskOrValueTask())
return true;
- if (method.OwningType.Module != method.Context.SystemModule)
+ if (ecmaMethod.OwningType.Module != ecmaMethod.Context.SystemModule)
return true;
return false;
}
- MethodIL GetMethodILForAsyncMethod(EcmaMethod method)
+ bool NeedsAsyncThunk(MethodDesc method)
+ {
+ if (method is not AsyncMethodVariant)
+ return false;
+ return !method.IsAsync;
+ }
+
+ MethodIL GetMethodILForAsyncMethod(MethodDesc method)
{
- Debug.Assert(method.IsAsync);
+ Debug.Assert(method.IsAsync && method is EcmaMethod);
if (method.Signature.ReturnsTaskOrValueTask())
{
- return AsyncThunkILEmitter.EmitTaskReturningThunk(method, ((CompilerTypeSystemContext)method.Context).GetAsyncVariantMethod(method));
+ return AsyncThunkILEmitter.EmitTaskReturningThunk(method, method.GetAsyncVariant());
}
// We only allow non-Task returning runtime async methods in CoreLib
// Skip this method
@@ -215,22 +256,21 @@ public override MethodIL GetMethodIL(MethodDesc method)
// portion of compilation, and CreateCrossModuleInlineableTokensForILBody
// will produce tokens which are order dependent thus violating the determinism
// principles of the compiler.
- if (!_manifestModuleWrappedMethods.TryGetValue(ecmaMethod, out var methodIL))
- {
- if (NeedsTaskReturningThunk(ecmaMethod))
- {
- methodIL = GetMethodILForAsyncMethod(ecmaMethod);
- }
- else
- {
- methodIL = EcmaMethodIL.Create(ecmaMethod);
- }
- }
+ if (_manifestModuleWrappedMethods.TryGetValue(ecmaMethod, out var methodIL))
+ return methodIL;
- if (methodIL != null)
+ return NeedsTaskReturningThunk(ecmaMethod) ?
+ GetMethodILForAsyncMethod(ecmaMethod)
+ : EcmaMethodIL.Create(ecmaMethod);
+ }
+ else if (method is AsyncMethodVariant amv)
+ {
+ if (_manifestModuleWrappedMethods.TryGetValue(amv, out var methodIL))
return methodIL;
- return null;
+ return NeedsAsyncThunk(amv) ?
+ null // Async thunks not supported yet
+ : new AsyncEcmaMethodIL(amv, EcmaMethodIL.Create((EcmaMethod)method.GetTargetOfAsyncVariant()));
}
else if (method is MethodForInstantiatedType || method is InstantiatedMethod)
{
@@ -247,6 +287,12 @@ public override MethodIL GetMethodIL(MethodDesc method)
return null;
return new InstantiatedMethodIL(method, methodDefinitionIL);
}
+ else if (method is AsyncResumptionStub ars)
+ {
+ if (_manifestModuleWrappedMethods.TryGetValue(ars, out var methodil))
+ return methodil;
+ return ars.EmitIL();
+ }
else
{
return null;
@@ -261,7 +307,7 @@ class ManifestModuleWrappedMethodIL : MethodIL, IEcmaMethodIL, IMethodTokensAreU
{
int _maxStack;
bool _isInitLocals;
- EcmaMethod _owningMethod;
+ MethodDesc _owningMethod;
ILExceptionRegion[] _exceptionRegions;
byte[] _ilBytes;
LocalVariableDefinition[] _locals;
@@ -276,8 +322,9 @@ public bool Initialize(MutableModule mutableModule, EcmaMethodIL wrappedMethod)
return Initialize(mutableModule, wrappedMethod, wrappedMethod.OwningMethod, true);
}
- public bool Initialize(MutableModule mutableModule, MethodIL wrappedMethod, EcmaMethod owningMethod, bool validateStandaloneMetadata)
+ public bool Initialize(MutableModule mutableModule, MethodIL wrappedMethod, MethodDesc owningMethod, bool validateStandaloneMetadata)
{
+ Debug.Assert(owningMethod.IsTypicalMethodDefinition);
HashSet methodsWhichCannotHaveAsyncVariants = null;
_methodsWithAsyncVariants = null;
@@ -288,7 +335,7 @@ public bool Initialize(MutableModule mutableModule, MethodIL wrappedMethod, Ecma
try
{
Debug.Assert(mutableModule.ModuleThatIsCurrentlyTheSourceOfNewReferences == null);
- mutableModule.ModuleThatIsCurrentlyTheSourceOfNewReferences = owningMethod.Module;
+ mutableModule.ModuleThatIsCurrentlyTheSourceOfNewReferences = ((EcmaMethod)owningMethod.GetPrimaryMethodDesc().GetTypicalMethodDefinition()).Module;
var owningMethodHandle = mutableModule.TryGetEntityHandle(owningMethod);
if (!owningMethodHandle.HasValue)
return false;
@@ -317,7 +364,7 @@ public bool Initialize(MutableModule mutableModule, MethodIL wrappedMethod, Ecma
ILTokenReplacer.Replace(_ilBytes, GetMutableModuleToken);
#if DEBUG
if (validateStandaloneMetadata)
- Debug.Assert(ReadyToRunStandaloneMethodMetadata.Compute(_owningMethod) != null);
+ Debug.Assert(ReadyToRunStandaloneMethodMetadata.Compute((EcmaMethod)_owningMethod.GetPrimaryMethodDesc().GetTypicalMethodDefinition()) != null);
#endif // DEBUG
}
finally
@@ -412,5 +459,28 @@ public override object GetObject(int token, NotFoundBehavior notFoundBehavior =
return result;
}
}
+
+ public sealed class AsyncEcmaMethodIL : MethodIL, IEcmaMethodIL
+ {
+ private readonly AsyncMethodVariant _variant;
+ private readonly EcmaMethodIL _ecmaIL;
+
+ public AsyncEcmaMethodIL(AsyncMethodVariant variant, EcmaMethodIL ecmaIL)
+ => (_variant, _ecmaIL) = (variant, ecmaIL);
+
+ // This is the reason we need this class - the method that owns the IL is the variant.
+ public override MethodDesc OwningMethod => _variant;
+
+ // Everything else dispatches to EcmaMethodIL
+ public override MethodDebugInformation GetDebugInfo() => _ecmaIL.GetDebugInfo();
+ public override ILExceptionRegion[] GetExceptionRegions() => _ecmaIL.GetExceptionRegions();
+ public override byte[] GetILBytes() => _ecmaIL.GetILBytes();
+ public override LocalVariableDefinition[] GetLocals() => _ecmaIL.GetLocals();
+ public override object GetObject(int token, NotFoundBehavior notFoundBehavior = NotFoundBehavior.Throw) => _ecmaIL.GetObject(token, notFoundBehavior);
+ public override bool IsInitLocals => _ecmaIL.IsInitLocals;
+ public override int MaxStack => _ecmaIL.MaxStack;
+
+ public IEcmaModule Module => _ecmaIL.Module;
+ }
}
}
diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ILCompiler.ReadyToRun.csproj b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ILCompiler.ReadyToRun.csproj
index cb7b001082722b..80f52e795daa13 100644
--- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ILCompiler.ReadyToRun.csproj
+++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ILCompiler.ReadyToRun.csproj
@@ -62,6 +62,7 @@
+
diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/JitInterface/CorInfoImpl.ReadyToRun.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/JitInterface/CorInfoImpl.ReadyToRun.cs
index 42fb1148659daf..0eccb3f357c94f 100644
--- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/JitInterface/CorInfoImpl.ReadyToRun.cs
+++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/JitInterface/CorInfoImpl.ReadyToRun.cs
@@ -134,8 +134,7 @@ public class MethodWithToken
public readonly bool OwningTypeNotDerivedFromToken;
public readonly TypeDesc OwningType;
-
- public MethodWithToken(MethodDesc method, ModuleToken token, TypeDesc constrainedType, bool unboxing, object context, TypeDesc devirtualizedMethodOwner = null)
+ public MethodWithToken(MethodDesc method, ModuleToken token, TypeDesc constrainedType, bool unboxing, TypeSystemEntity context, TypeDesc devirtualizedMethodOwner = null)
{
Debug.Assert(!method.IsUnboxingThunk());
Method = method;
@@ -143,15 +142,9 @@ public MethodWithToken(MethodDesc method, ModuleToken token, TypeDesc constraine
ConstrainedType = constrainedType;
Unboxing = unboxing;
OwningType = GetMethodTokenOwningType(this, constrainedType, context, devirtualizedMethodOwner, out OwningTypeNotDerivedFromToken);
- if (method.IsAsync && method.IsAsyncVariant() && token.Module is MutableModule)
- {
- var ecmaMethod = (EcmaMethod)method.GetTypicalMethodDefinition().GetPrimaryMethodDesc();
- Token = new (ecmaMethod.Module, ecmaMethod.Handle);
- OwningTypeNotDerivedFromToken = true;
- }
}
- private static TypeDesc GetMethodTokenOwningType(MethodWithToken methodToken, TypeDesc constrainedType, object context, TypeDesc devirtualizedMethodOwner, out bool owningTypeNotDerivedFromToken)
+ private static TypeDesc GetMethodTokenOwningType(MethodWithToken methodToken, TypeDesc constrainedType, TypeSystemEntity context, TypeDesc devirtualizedMethodOwner, out bool owningTypeNotDerivedFromToken)
{
ModuleToken moduleToken = methodToken.Token;
owningTypeNotDerivedFromToken = false;
@@ -186,7 +179,7 @@ private static TypeDesc GetMethodTokenOwningType(MethodWithToken methodToken, Ty
return methodToken.Method.OwningType;
}
- TypeDesc HandleContext(IEcmaModule module, EntityHandle handle, TypeDesc methodTargetOwner, TypeDesc constrainedType, object context, TypeDesc devirtualizedMethodOwner, ref bool owningTypeNotDerivedFromToken)
+ TypeDesc HandleContext(IEcmaModule module, EntityHandle handle, TypeDesc methodTargetOwner, TypeDesc constrainedType, TypeSystemEntity context, TypeDesc devirtualizedMethodOwner, ref bool owningTypeNotDerivedFromToken)
{
var tokenOnlyOwningType = module.GetType(handle);
TypeDesc actualOwningType;
@@ -359,6 +352,8 @@ public void AppendMangledName(NameMangler nameMangler, Utf8StringBuilder sb)
sb.Append("; UNBOXING"u8);
if (Method.IsAsyncVariant())
sb.Append("; ASYNC"u8);
+ if (Method is AsyncResumptionStub)
+ sb.Append("; RESUME"u8);
}
public override string ToString()
@@ -565,8 +560,7 @@ public static bool ShouldSkipCompilation(InstructionSetSupport instructionSetSup
public static bool ShouldCodeNotBeCompiledIntoFinalImage(InstructionSetSupport instructionSetSupport, MethodDesc method)
{
- EcmaMethod ecmaMethod = method.GetTypicalMethodDefinition().GetPrimaryMethodDesc() as EcmaMethod;
-
+ EcmaMethod ecmaMethod = (EcmaMethod)(method.GetPrimaryMethodDesc().GetTypicalMethodDefinition());
var metadataReader = ecmaMethod.MetadataReader;
var stringComparer = metadataReader.StringComparer;
@@ -960,8 +954,8 @@ private void getReadyToRunDelegateCtorHelper(ref CORINFO_RESOLVED_TOKEN pTargetM
MethodDesc targetMethodDesc = HandleToObject(pTargetMethod.hMethod);
Debug.Assert(!targetMethodDesc.IsUnboxingThunk());
- var typeOrMethodContext = (pTargetMethod.tokenContext == contextFromMethodBeingCompiled()) ?
- MethodBeingCompiled : HandleToObject((void*)pTargetMethod.tokenContext);
+ TypeSystemEntity typeOrMethodContext = (TypeSystemEntity)((pTargetMethod.tokenContext == contextFromMethodBeingCompiled()) ?
+ MethodBeingCompiled : HandleToObject((void*)pTargetMethod.tokenContext));
TypeDesc constrainedType = null;
if (targetConstraint != 0)
@@ -1280,17 +1274,32 @@ private ISymbolNode GetHelperFtnUncached(CorInfoHelpFunc ftnNum)
id = ReadyToRunHelper.ReversePInvokeExit;
break;
+ // TODO: Encoding these as r2r helpers creates eager fixups. During fixup processing, the runtime asserts that no typeloading happens.
+ // However, in resolving these methods, they are loaded and their containing types are loaded, and the assertion fails.
case CorInfoHelpFunc.CORINFO_HELP_ALLOC_CONTINUATION:
- id = ReadyToRunHelper.AllocContinuation;
- break;
-
+ {
+ var method = _compilation.NodeFactory.TypeSystemContext.GetCoreLibEntryPoint("System.Runtime.CompilerServices"u8, "AsyncHelpers"u8, "AllocContinuation"u8, null);
+ var methodWithToken = new MethodWithToken(method, _compilation.CompilationModuleGroup.Resolver.GetModuleTokenForMethod(method, true, true), null, false, null);
+ return _compilation.NodeFactory.MethodEntrypoint(methodWithToken, false, false, false);
+ //id = ReadyToRunHelper.AllocContinuation;
+ //break;
+ }
case CorInfoHelpFunc.CORINFO_HELP_ALLOC_CONTINUATION_METHOD:
- id = ReadyToRunHelper.AllocContinuationMethod;
- break;
-
+ {
+ var method = _compilation.NodeFactory.TypeSystemContext.GetCoreLibEntryPoint("System.Runtime.CompilerServices"u8, "AsyncHelpers"u8, "AllocContinuationMethod"u8, null);
+ var methodWithToken = new MethodWithToken(method, _compilation.CompilationModuleGroup.Resolver.GetModuleTokenForMethod(method, true, true), null, false, null);
+ return _compilation.NodeFactory.MethodEntrypoint(methodWithToken, false, false, false);
+ //id = ReadyToRunHelper.AllocContinuationMethod;
+ //break;
+ }
case CorInfoHelpFunc.CORINFO_HELP_ALLOC_CONTINUATION_CLASS:
- id = ReadyToRunHelper.AllocContinuationClass;
- break;
+ {
+ var method = _compilation.NodeFactory.TypeSystemContext.GetCoreLibEntryPoint("System.Runtime.CompilerServices"u8, "AsyncHelpers"u8, "AllocContinuationClass"u8, null);
+ var methodWithToken = new MethodWithToken(method, _compilation.CompilationModuleGroup.Resolver.GetModuleTokenForMethod(method, true, true), null, false, null);
+ return _compilation.NodeFactory.MethodEntrypoint(methodWithToken, false, false, false);
+ //id = ReadyToRunHelper.AllocContinuationClass;
+ //break;
+ }
case CorInfoHelpFunc.CORINFO_HELP_INITCLASS:
case CorInfoHelpFunc.CORINFO_HELP_INITINSTCLASS:
@@ -1320,7 +1329,9 @@ private ISymbolNode GetHelperFtnUncached(CorInfoHelpFunc ftnNum)
private void getFunctionEntryPoint(CORINFO_METHOD_STRUCT_* ftn, ref CORINFO_CONST_LOOKUP pResult, CORINFO_ACCESS_FLAGS accessFlags)
{
- throw new RequiresRuntimeJitException(HandleToObject(ftn).ToString());
+ var method = HandleToObject(ftn);
+ var entrypoint = _compilation.NodeFactory.MethodEntrypoint(new MethodWithToken(method, _compilation.NodeFactory.Resolver.GetModuleTokenForMethod(method, true, true), null, false, MethodBeingCompiled), false, false, false);
+ pResult = CreateConstLookupToSymbol(entrypoint);
}
private bool canTailCall(CORINFO_METHOD_STRUCT_* callerHnd, CORINFO_METHOD_STRUCT_* declaredCalleeHnd, CORINFO_METHOD_STRUCT_* exactCalleeHnd, bool fIsTailPrefix)
@@ -1367,7 +1378,7 @@ private FieldWithToken ComputeFieldWithToken(FieldDesc field, ref CORINFO_RESOLV
private MethodWithToken ComputeMethodWithToken(MethodDesc method, ref CORINFO_RESOLVED_TOKEN pResolvedToken, TypeDesc constrainedType, bool unboxing)
{
- ModuleToken token = HandleToModuleToken(ref pResolvedToken, method, out object context, ref constrainedType);
+ ModuleToken token = HandleToModuleToken(ref pResolvedToken, method, out TypeSystemEntity context, ref constrainedType);
TypeDesc devirtualizedMethodOwner = null;
if (pResolvedToken.tokenType == CorInfoTokenKind.CORINFO_TOKENKIND_DevirtualizedMethod)
@@ -1378,7 +1389,7 @@ private MethodWithToken ComputeMethodWithToken(MethodDesc method, ref CORINFO_RE
return new MethodWithToken(method, token, constrainedType: constrainedType, unboxing: unboxing, context: context, devirtualizedMethodOwner: devirtualizedMethodOwner);
}
- private ModuleToken HandleToModuleToken(ref CORINFO_RESOLVED_TOKEN pResolvedToken, MethodDesc methodDesc, out object context, ref TypeDesc constrainedType)
+ private ModuleToken HandleToModuleToken(ref CORINFO_RESOLVED_TOKEN pResolvedToken, MethodDesc methodDesc, out TypeSystemEntity context, ref TypeDesc constrainedType)
{
if (methodDesc != null && (_compilation.NodeFactory.CompilationModuleGroup.VersionsWithMethodBody(methodDesc)
|| (pResolvedToken.tokenType == CorInfoTokenKind.CORINFO_TOKENKIND_DevirtualizedMethod)
@@ -1437,7 +1448,11 @@ private ModuleToken HandleToModuleToken(ref CORINFO_RESOLVED_TOKEN pResolvedToke
// It's okay to strip the instantiation away because we don't need a MethodSpec
// token - SignatureBuilder will generate the generic method signature
// using instantiation parameters from the MethodDesc entity.
- resultMethod = resultMethod.GetTypicalMethodDefinition().GetPrimaryMethodDesc();
+ if (resultMethod is PInvokeTargetNativeMethod pInvoke)
+ {
+ resultMethod = pInvoke.Target;
+ }
+ resultMethod = resultMethod.GetPrimaryMethodDesc().GetTypicalMethodDefinition();
if (!_compilation.NodeFactory.CompilationModuleGroup.VersionsWithType(resultMethod.OwningType))
{
@@ -1462,24 +1477,7 @@ private ModuleToken HandleToModuleToken(ref CORINFO_RESOLVED_TOKEN pResolvedToke
}
else
{
- if (resultDef is EcmaType ecmaType)
- {
- if (!_compilation.NodeFactory.CompilationModuleGroup.VersionsWithType(ecmaType))
- {
- ModuleToken result = _compilation.NodeFactory.Resolver.GetModuleTokenForType(ecmaType, allowDynamicallyCreatedReference: true, throwIfNotFound: true);
- return result;
- }
- token = (mdToken)MetadataTokens.GetToken(ecmaType.Handle);
- module = ecmaType.Module;
- }
- else
- {
- // To replace !!0, we need to find the token for a !!0 TypeSpec within the image.
- Debug.Assert(resultDef is SignatureMethodVariable);
- Debug.Assert(((SignatureMethodVariable)resultDef).Index == 0);
- module = (EcmaModule)((MetadataType)methodILDef.OwningMethod.OwningType).Module;
- token = FindGenericMethodArgTypeSpec((EcmaModule)module);
- }
+ return GetModuleTokenForType((TypeSystemEntity)resultDef);
}
}
else
@@ -1488,6 +1486,30 @@ private ModuleToken HandleToModuleToken(ref CORINFO_RESOLVED_TOKEN pResolvedToke
}
return new ModuleToken(module, token);
+
+ ModuleToken GetModuleTokenForType(TypeSystemEntity resultDef)
+ {
+ switch (resultDef)
+ {
+ case SignatureMethodVariable sigMethod:
+ Debug.Assert(sigMethod.Index == 0);
+ module = (EcmaModule)((MetadataType)methodILDef.OwningMethod.OwningType).Module;
+ token = FindGenericMethodArgTypeSpec((EcmaModule)module);
+ return new ModuleToken(module, token);
+ case ParameterizedType paramType:
+ return _compilation.NodeFactory.Resolver.GetModuleTokenForType(paramType, allowDynamicallyCreatedReference: true, throwIfNotFound: true);
+ case EcmaType ecmaType:
+ if (!_compilation.NodeFactory.CompilationModuleGroup.VersionsWithType(ecmaType))
+ {
+ return _compilation.NodeFactory.Resolver.GetModuleTokenForType(ecmaType, allowDynamicallyCreatedReference: true, throwIfNotFound: true);
+ }
+ token = (mdToken)MetadataTokens.GetToken(ecmaType.Handle);
+ module = ecmaType.Module;
+ return new ModuleToken(module, token);
+ default:
+ throw new NotImplementedException($"Unsupported token resolution for {resultDef.GetType()}");
+ }
+ }
}
private InfoAccessType constructStringLiteral(CORINFO_MODULE_STRUCT_* module, mdToken metaTok, ref void* ppValue)
@@ -1828,18 +1850,18 @@ private void getFieldInfo(ref CORINFO_RESOLVED_TOKEN pResolvedToken, CORINFO_MET
fieldOffset = 0;
}
else
- if (helperId != ReadyToRunHelperId.Invalid)
- {
- if (_compilation.SymbolNodeFactory.VerifyTypeAndFieldLayout && (fieldOffset <= FieldFixupSignature.MaxCheckableOffset))
+ if (helperId != ReadyToRunHelperId.Invalid)
{
- // ENCODE_CHECK_FIELD_OFFSET
- AddPrecodeFixup(_compilation.SymbolNodeFactory.CheckFieldOffset(ComputeFieldWithToken(field, ref pResolvedToken)));
- }
+ if (_compilation.SymbolNodeFactory.VerifyTypeAndFieldLayout && (fieldOffset <= FieldFixupSignature.MaxCheckableOffset))
+ {
+ // ENCODE_CHECK_FIELD_OFFSET
+ AddPrecodeFixup(_compilation.SymbolNodeFactory.CheckFieldOffset(ComputeFieldWithToken(field, ref pResolvedToken)));
+ }
- pResult->fieldLookup = CreateConstLookupToSymbol(
- _compilation.SymbolNodeFactory.CreateReadyToRunHelper(helperId, field.OwningType)
- );
- }
+ pResult->fieldLookup = CreateConstLookupToSymbol(
+ _compilation.SymbolNodeFactory.CreateReadyToRunHelper(helperId, field.OwningType)
+ );
+ }
}
}
else
@@ -1934,7 +1956,7 @@ private void ceeInfoGetCallInfo(
throw new RequiresRuntimeJitException(callerMethod.ToString() + " -> " + originalMethod.ToString());
}
- callerModule = ((EcmaMethod)callerMethod.GetTypicalMethodDefinition()).Module;
+ callerModule = ((EcmaMethod)callerMethod.GetPrimaryMethodDesc().GetTypicalMethodDefinition()).Module;
bool isCallVirt = (flags & CORINFO_CALLINFO_FLAGS.CORINFO_CALLINFO_CALLVIRT) != 0;
bool isLdftn = (flags & CORINFO_CALLINFO_FLAGS.CORINFO_CALLINFO_LDFTN) != 0;
bool isStaticVirtual = (originalMethod.Signature.IsStatic && originalMethod.IsVirtual);
@@ -2499,8 +2521,8 @@ private void getCallInfo(ref CORINFO_RESOLVED_TOKEN pResolvedToken, CORINFO_RESO
// If the abi of the method isn't stable, this will cause a usage of the RequiresRuntimeJitSymbol, which will trigger a RequiresRuntimeJitException
UpdateConstLookupWithRequiresRuntimeJitSymbolIfNeeded(ref pResult->codePointerOrStubLookup.constLookup, targetMethod);
- }
- break;
+ }
+ break;
case CORINFO_CALL_KIND.CORINFO_CALL_CODE_POINTER:
diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/TypeSystem/MethodDescExtensions.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/TypeSystem/MethodDescExtensions.cs
index 935a160ee4f6ea..a39db0fd7c9e80 100644
--- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/TypeSystem/MethodDescExtensions.cs
+++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/TypeSystem/MethodDescExtensions.cs
@@ -21,12 +21,11 @@ public static MethodDesc GetPrimaryMethodDesc(this MethodDesc method)
{
return method.GetUnboxedMethod().GetPrimaryMethodDesc();
}
- return method switch
+ if (method is AsyncResumptionStub resumptionStub)
{
- PInvokeTargetNativeMethod pinvokeTarget => pinvokeTarget.Target,
- AsyncResumptionStub resumptionStub => resumptionStub.TargetMethod.GetPrimaryMethodDesc(),
- _ => method,
- };
+ return resumptionStub.TargetMethod.GetPrimaryMethodDesc();
+ }
+ return method;
}
public static bool IsPrimaryMethodDesc(this MethodDesc method)
diff --git a/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/ReadyToRunReader.cs b/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/ReadyToRunReader.cs
index a960fcfdee3a2a..e32a980b94e739 100644
--- a/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/ReadyToRunReader.cs
+++ b/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/ReadyToRunReader.cs
@@ -1015,6 +1015,10 @@ private DecodedMethodSignature DecodeMethodSignature(ref IAssemblyMetadata mdRea
{
signaturePrefixes.Add("[ASYNC]");
}
+ if ((methodFlags & (uint)ReadyToRunMethodSigFlags.READYTORUN_METHOD_SIG_ResumptionStub) != 0)
+ {
+ signaturePrefixes.Add("[RESUME]");
+ }
return new DecodedMethodSignature(owningType, methodHandle, methodTypeArgs, constrainedType, signaturePrefixes.ToArray());
}
diff --git a/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/ReadyToRunSignature.cs b/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/ReadyToRunSignature.cs
index 0ac72684e5d4f4..0931c018e7c04d 100644
--- a/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/ReadyToRunSignature.cs
+++ b/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/ReadyToRunSignature.cs
@@ -1025,6 +1025,10 @@ public string GetMethodWithFlags(ReadyToRunMethodSigFlags flags, string method)
{
builder.Append("[ASYNC] ");
}
+ if ((flags & ReadyToRunMethodSigFlags.READYTORUN_METHOD_SIG_ResumptionStub) != 0)
+ {
+ builder.Append("[RESUME] ");
+ }
builder.Append(method);
return builder.ToString();
}
@@ -2038,7 +2042,7 @@ private void ParseHelper(StringBuilder builder)
break;
default:
- throw new BadImageFormatException();
+ throw new BadImageFormatException(helperType.ToString());
}
}
diff --git a/src/coreclr/vm/frames.cpp b/src/coreclr/vm/frames.cpp
index 5c7464439ed873..dc03824e53936b 100644
--- a/src/coreclr/vm/frames.cpp
+++ b/src/coreclr/vm/frames.cpp
@@ -2229,11 +2229,14 @@ void ComputeCallRefMap(MethodDesc* pMD,
{
msig.SetHasParamTypeArg();
}
+ }
- if (pMD->IsAsyncMethod())
- {
- msig.SetIsAsyncCall();
- }
+ // The async continuation is a caller-side argument that is always passed
+ // regardless of whether the dispatch target has been resolved, unlike the
+ // instantiation argument which is an implementation detail of shared generics.
+ if (pMD->IsAsyncMethod())
+ {
+ msig.SetIsAsyncCall();
}
ArgIterator argit(&msig);
diff --git a/src/coreclr/vm/ilstubcache.cpp b/src/coreclr/vm/ilstubcache.cpp
index 7fe8b6d3951945..595b4db0536705 100644
--- a/src/coreclr/vm/ilstubcache.cpp
+++ b/src/coreclr/vm/ilstubcache.cpp
@@ -356,6 +356,98 @@ MethodDesc* ILStubCache::CreateNewMethodDesc(LoaderHeap* pCreationHeap, MethodTa
RETURN pMD;
}
+// Creates a DynamicMethodDesc that wraps pre-compiled R2R stub code.
+// Unlike regular IL stubs, this does not create a resolver or precode - it points
+// directly to the R2R native code.
+MethodDesc* ILStubCache::CreateR2RBackedILStub(
+ LoaderAllocator* pAllocator,
+ MethodTable* pMT,
+ PCODE r2rEntryPoint,
+ DWORD stubType,
+ PCCOR_SIGNATURE pSig,
+ DWORD cbSig,
+ BOOL isAsync,
+ AllocMemTracker* pamTracker)
+{
+ CONTRACT(MethodDesc*)
+ {
+ STANDARD_VM_CHECK;
+ PRECONDITION(CheckPointer(pAllocator));
+ PRECONDITION(CheckPointer(pMT));
+ PRECONDITION(r2rEntryPoint != (PCODE)NULL);
+ PRECONDITION(stubType != DynamicMethodDesc::StubNotSet);
+ POSTCONDITION(CheckPointer(RETVAL));
+ }
+ CONTRACT_END;
+
+ DynamicMethodDesc::ILStubType ilStubType = (DynamicMethodDesc::ILStubType)stubType;
+
+ LoaderHeap* pCreationHeap = pAllocator->GetHighFrequencyHeap();
+
+ MethodDescChunk* pChunk = MethodDescChunk::CreateChunk(
+ pCreationHeap,
+ 1, // count
+ mcDynamic, // classification
+ TRUE, // fNonVtableSlot - Dynamic methods don't have vtable slots
+ TRUE, // fNativeCodeSlot - we will set the native code pointer directly to the R2R entry point
+ isAsync, // HasAsyncMethodData
+ pMT,
+ pamTracker);
+
+ DynamicMethodDesc* pMD = (DynamicMethodDesc*)pChunk->GetFirstMethodDesc();
+
+ pMD->SetMemberDef(0);
+ pMD->SetSlot(MethodTable::NO_SLOT);
+
+ if (isAsync)
+ {
+ pMD->SetHasAsyncMethodData();
+ pMD->GetAddrOfAsyncMethodData()->flags = AsyncMethodFlags::AsyncCall;
+ }
+
+ // Determine static vs instance from the signature calling convention
+ SigPointer sigPtr(pSig, cbSig);
+ uint32_t callConvInfo;
+ IfFailThrow(sigPtr.GetCallingConvInfo(&callConvInfo));
+
+ if (callConvInfo & CORINFO_CALLCONV_HASTHIS)
+ {
+ pMD->InitializeFlags(DynamicMethodDesc::FlagPublic |
+ DynamicMethodDesc::FlagIsILStub);
+ }
+ else
+ {
+ pMD->SetStatic();
+ pMD->InitializeFlags(DynamicMethodDesc::FlagPublic |
+ DynamicMethodDesc::FlagStatic |
+ DynamicMethodDesc::FlagIsILStub);
+ }
+
+ pMD->SetILStubType(ilStubType);
+
+ // No resolver needed - code already exists in R2R image
+ pMD->m_pResolver = nullptr;
+
+ pMD->m_pszMethodName = GetStubMethodName(ilStubType);
+
+ // Copy the signature into the loader heap
+ PVOID pNewSig = pamTracker->Track(pCreationHeap->AllocMem(S_SIZE_T(cbSig)));
+ memcpy(pNewSig, pSig, cbSig);
+ pMD->SetStoredMethodSig((PCCOR_SIGNATURE)pNewSig, cbSig);
+
+ // Set the native code directly - no precode needed since code already exists
+ pMD->SetNativeCodeInterlocked(r2rEntryPoint);
+
+#ifdef _DEBUG
+ pMD->m_pszDebugMethodName = pMD->m_pszMethodName;
+ pMD->m_pszDebugClassName = "ILStubClass";
+ pMD->m_pszDebugMethodSignature = FormatSig(pMD, pCreationHeap, pamTracker);
+ pMD->m_pDebugMethodTable = pMT;
+#endif // _DEBUG
+
+ RETURN pMD;
+}
+
//
// This will get or create a MethodTable in the Module/AppDomain on which
// we can place a new IL stub MethodDesc.
diff --git a/src/coreclr/vm/ilstubcache.h b/src/coreclr/vm/ilstubcache.h
index 52be99d9fb8ef1..0157530b54a7fb 100644
--- a/src/coreclr/vm/ilstubcache.h
+++ b/src/coreclr/vm/ilstubcache.h
@@ -76,6 +76,19 @@ class ILStubCache final
ILStubLinker* pStubLinker,
BOOL isAsync = FALSE);
+ // Creates a DynamicMethodDesc that wraps pre-compiled R2R stub code.
+ // Unlike regular IL stubs, this does not create a resolver or precode - it points
+ // directly to the R2R native code.
+ static MethodDesc* CreateR2RBackedILStub(
+ LoaderAllocator* pAllocator,
+ MethodTable* pMT,
+ PCODE r2rEntryPoint,
+ DWORD stubType, // DynamicMethodDesc::ILStubType
+ PCCOR_SIGNATURE pSig,
+ DWORD cbSig,
+ BOOL isAsync,
+ AllocMemTracker* pamTracker);
+
MethodTable * GetStubMethodTable()
{
LIMITED_METHOD_CONTRACT;
diff --git a/src/coreclr/vm/method.hpp b/src/coreclr/vm/method.hpp
index d8a4181d34766a..bb2cf9b7e7dd9d 100644
--- a/src/coreclr/vm/method.hpp
+++ b/src/coreclr/vm/method.hpp
@@ -3016,6 +3016,13 @@ class DynamicMethodDesc : public StoredSigMethodDesc
_ASSERTE(IsILStub());
return GetILStubType() == DynamicMethodDesc::StubDelegateShuffleThunk;
}
+ bool IsAsyncResumptionStub() const
+ {
+ LIMITED_METHOD_DAC_CONTRACT;
+ _ASSERTE(IsILStub());
+ ILStubType type = GetILStubType();
+ return type == DynamicMethodDesc::StubAsyncResume;
+ }
// Whether the stub takes a context argument that is an interop MethodDesc.
// See RequiresMDContextArg() for the non-stub version.
diff --git a/src/coreclr/vm/readytoruninfo.cpp b/src/coreclr/vm/readytoruninfo.cpp
index 3cd42e18b8ff5a..d7b33699920eb5 100644
--- a/src/coreclr/vm/readytoruninfo.cpp
+++ b/src/coreclr/vm/readytoruninfo.cpp
@@ -17,6 +17,8 @@
#include "wellknownattributes.h"
#include "nativeimage.h"
#include "dn-stdio.h"
+#include "ilstubcache.h"
+#include "sigbuilder.h"
#ifdef FEATURE_PERFMAP
#include "perfmap.h"
@@ -1010,8 +1012,6 @@ static bool SigMatchesMethodDesc(MethodDesc* pMD, SigPointer &sig, ModuleBase *
{
STANDARD_VM_CONTRACT;
- _ASSERTE(!pMD->IsAsyncVariantMethod());
-
ModuleBase *pOrigModule = pModule;
ZapSig::Context zapSigContext(pModule, (void *)pModule, ZapSig::NormalTokens);
ZapSig::Context * pZapSigContext = &zapSigContext;
@@ -1099,10 +1099,187 @@ static bool SigMatchesMethodDesc(MethodDesc* pMD, SigPointer &sig, ModuleBase *
IfFailThrow(sig.SkipExactlyOne());
}
}
+ bool sigIsAsync = (methodFlags & ENCODE_METHOD_SIG_AsyncVariant) != 0;
+ if (sigIsAsync != pMD->IsAsyncVariantMethod())
+ {
+ return false;
+ }
+
+ bool sigIsResume = (methodFlags & ENCODE_METHOD_SIG_ResumptionStub) != 0;
+ bool methodIsResume = pMD->IsDynamicMethod() && ((DynamicMethodDesc*)pMD)->IsILStub() && ((DynamicMethodDesc*)pMD)->IsAsyncResumptionStub();
+ if (sigIsResume != methodIsResume)
+ {
+ return false;
+ }
+
+ return true;
+}
+
+// Checks if sig matches pMD for resumption stub lookup.
+// This is similar to SigMatchesMethodDesc but specifically looks for
+// entries with ENCODE_METHOD_SIG_ResumptionStub flag that correspond
+// to the given async variant method.
+static bool SigMatchesResumptionStubForMethod(MethodDesc* pAsyncMD, SigPointer &sig, ModuleBase * pModule)
+{
+ STANDARD_VM_CONTRACT;
+
+ _ASSERTE(pAsyncMD->IsAsyncVariantMethod());
+
+ ModuleBase *pOrigModule = pModule;
+ ZapSig::Context zapSigContext(pModule, (void *)pModule, ZapSig::NormalTokens);
+ ZapSig::Context * pZapSigContext = &zapSigContext;
+
+ uint32_t methodFlags;
+ IfFailThrow(sig.GetData(&methodFlags));
+
+ // Must have ResumptionStub flag set
+ if ((methodFlags & ENCODE_METHOD_SIG_ResumptionStub) == 0)
+ return false;
+
+ _ASSERTE((methodFlags & ENCODE_METHOD_SIG_SlotInsteadOfToken) == 0);
+ _ASSERTE(((methodFlags & (ENCODE_METHOD_SIG_MemberRefToken | ENCODE_METHOD_SIG_UpdateContext)) == 0) ||
+ ((methodFlags & (ENCODE_METHOD_SIG_MemberRefToken | ENCODE_METHOD_SIG_UpdateContext)) == (ENCODE_METHOD_SIG_MemberRefToken | ENCODE_METHOD_SIG_UpdateContext)));
+
+ if ( methodFlags & ENCODE_METHOD_SIG_UpdateContext)
+ {
+ uint32_t updatedModuleIndex;
+ IfFailThrow(sig.GetData(&updatedModuleIndex));
+ pModule = pZapSigContext->GetZapSigModule()->GetModuleFromIndex(updatedModuleIndex);
+ }
+
+ if (methodFlags & ENCODE_METHOD_SIG_OwnerType)
+ {
+ PCCOR_SIGNATURE pSigType;
+ uint32_t cbSigType;
+ sig.GetSignature(&pSigType, &cbSigType);
+ if (!ZapSig::CompareSignatureToTypeHandle(pSigType, pModule, TypeHandle(pAsyncMD->GetMethodTable()), pZapSigContext))
+ return false;
+
+ IfFailThrow(sig.SkipExactlyOne());
+ }
+
+ RID rid;
+ IfFailThrow(sig.GetData(&rid));
+
+ if ((methodFlags & ENCODE_METHOD_SIG_MemberRefToken) != 0)
+ {
+ IMDInternalImport * pInternalImport = pModule->GetMDImport();
+
+ LPCUTF8 szMember;
+ PCCOR_SIGNATURE pSig;
+ DWORD cSig;
+
+ IfFailThrow(pInternalImport->GetNameAndSigOfMemberRef(TokenFromRid(rid, mdtMemberRef), &pSig, &cSig, &szMember));
+
+ _ASSERTE(!isCallConv(MetaSig::GetCallingConvention(Signature(pSig, cSig)), IMAGE_CEE_CS_CALLCONV_FIELD));
+
+ if (strcmp(szMember, pAsyncMD->GetName()) != 0)
+ return false;
+
+ PCCOR_SIGNATURE pTargetMethodSig;
+ DWORD cTargetMethodSig;
+
+ pAsyncMD->GetSig(&pTargetMethodSig, &cTargetMethodSig);
+ if (!MetaSig::CompareMethodSigs(pSig, cSig, pModule, NULL, pTargetMethodSig, cTargetMethodSig, pAsyncMD->GetModule(), NULL, FALSE))
+ return false;
+
+ rid = RidFromToken(pAsyncMD->GetMemberDef());
+ }
+
+ if (RidFromToken(pAsyncMD->GetMemberDef()) != rid)
+ return false;
+
+ if (methodFlags & ENCODE_METHOD_SIG_MethodInstantiation)
+ {
+ uint32_t numGenericArgs;
+ IfFailThrow(sig.GetData(&numGenericArgs));
+ Instantiation inst = pAsyncMD->GetMethodInstantiation();
+ if (numGenericArgs != inst.GetNumArgs())
+ return false;
+
+ for (uint32_t i = 0; i < numGenericArgs; i++)
+ {
+ PCCOR_SIGNATURE pSigArg;
+ uint32_t cbSigArg;
+ sig.GetSignature(&pSigArg, &cbSigArg);
+ if (!ZapSig::CompareSignatureToTypeHandle(pSigArg, pOrigModule, inst[i], pZapSigContext))
+ return false;
+
+ IfFailThrow(sig.SkipExactlyOne());
+ }
+ }
return true;
}
+// Looks up the R2R entry point for the resumption stub corresponding to the given async variant method.
+// Returns NULL if not found. If found, also returns the runtime function index via pRuntimeFunctionIndex.
+PCODE ReadyToRunInfo::LookupResumptionStubEntryPoint(MethodDesc* pAsyncVariantMD, PrepareCodeConfig* pConfig, BOOL fFixups, uint* pRuntimeFunctionIndex)
+{
+ STANDARD_VM_CONTRACT;
+
+ _ASSERTE(pAsyncVariantMD->IsAsyncVariantMethod());
+
+ if (m_instMethodEntryPoints.IsNull())
+ return (PCODE)NULL;
+
+ // The resumption stub is stored with the same hash as the async variant method
+ NativeHashtable::Enumerator lookup = m_instMethodEntryPoints.Lookup(GetVersionResilientMethodHashCode(pAsyncVariantMD));
+ NativeParser entryParser;
+ uint offset = (uint)-1;
+
+ while (lookup.GetNext(entryParser))
+ {
+ PCCOR_SIGNATURE pBlob = (PCCOR_SIGNATURE)entryParser.GetBlob();
+ SigPointer sig(pBlob);
+ if (SigMatchesResumptionStubForMethod(pAsyncVariantMD, sig, m_pModule))
+ {
+ offset = entryParser.GetOffset() + (uint)(sig.GetPtr() - pBlob);
+ break;
+ }
+ }
+
+ if (offset == (uint)-1)
+ return (PCODE)NULL;
+
+ uint id;
+ offset = m_nativeReader.DecodeUnsigned(offset, &id);
+
+ if (id & 1)
+ {
+ if (id & 2)
+ {
+ uint val;
+ m_nativeReader.DecodeUnsigned(offset, &val);
+ offset -= val;
+ }
+
+ if (fFixups)
+ {
+ BOOL mayUsePrecompiledPInvokeMethods = TRUE;
+ mayUsePrecompiledPInvokeMethods = !pConfig->IsForMulticoreJit();
+
+ if (!m_pModule->FixupDelayList(dac_cast(GetImage()->GetBase()) + offset, mayUsePrecompiledPInvokeMethods))
+ {
+ pConfig->SetReadyToRunRejectedPrecompiledCode();
+ return (PCODE)NULL;
+ }
+ }
+
+ id >>= 2;
+ }
+ else
+ {
+ id >>= 1;
+ }
+
+ _ASSERTE(id < m_nRuntimeFunctions);
+ if (pRuntimeFunctionIndex != nullptr)
+ *pRuntimeFunctionIndex = id;
+
+ return dac_cast(GetImage()->GetBase()) + m_pRuntimeFunctions[id].BeginAddress;
+}
+
bool ReadyToRunInfo::GetPgoInstrumentationData(MethodDesc * pMD, BYTE** pAllocatedMemory, ICorJitInfo::PgoInstrumentationSchema**ppSchema, UINT *pcSchema, BYTE** pInstrumentationData)
{
STANDARD_VM_CONTRACT;
@@ -1196,14 +1373,12 @@ PCODE ReadyToRunInfo::GetEntryPoint(MethodDesc * pMD, PrepareCodeConfig* pConfig
if (ReadyToRunCodeDisabled())
goto done;
- // TODO: (async) R2R support for async variants (https://github.com/dotnet/runtime/issues/121559)
- if (pMD->IsAsyncVariantMethod())
- goto done;
-
ETW::MethodLog::GetR2RGetEntryPointStart(pMD);
uint offset;
- if (pMD->HasClassOrMethodInstantiation())
+ // Async variants and resumption stubs are stored in the instance methods table
+ if (pMD->HasClassOrMethodInstantiation()
+ || pMD->IsAsyncVariantMethod())
{
if (m_instMethodEntryPoints.IsNull())
goto done;
@@ -1287,6 +1462,53 @@ PCODE ReadyToRunInfo::GetEntryPoint(MethodDesc * pMD, PrepareCodeConfig* pConfig
pEntryPoint = dac_cast(GetImage()->GetBase()) + m_pRuntimeFunctions[id].BeginAddress;
m_pCompositeInfo->SetMethodDescForEntryPointInNativeImage(pEntryPoint, pMD);
+ // For async variant methods, we also need to look up the corresponding resumption stub
+ // and create a DynamicMethodDesc wrapper for it so that GC stack walks work correctly.
+ if (pMD->IsAsyncVariantMethod())
+ {
+ uint stubRuntimeFunctionIndex = 0;
+ PCODE stubEntryPoint = LookupResumptionStubEntryPoint(pMD, pConfig, fFixups, &stubRuntimeFunctionIndex);
+ if (stubEntryPoint != (PCODE)NULL)
+ {
+ // Create a DynamicMethodDesc wrapper for the R2R resumption stub
+ AllocMemTracker amTracker;
+ MethodTable* pStubMT = m_pModule->GetILStubCache()->GetOrCreateStubMethodTable(m_pModule);
+
+ // Build the resumption stub signature: object(object, ref byte)
+ // This matches BuildResumptionStubSignature in jitinterface.cpp
+ SigBuilder sigBuilder;
+ sigBuilder.AppendByte(IMAGE_CEE_CS_CALLCONV_DEFAULT);
+ sigBuilder.AppendData(2); // 2 arguments
+ sigBuilder.AppendElementType(ELEMENT_TYPE_OBJECT); // return type: object (continuation)
+ sigBuilder.AppendElementType(ELEMENT_TYPE_OBJECT); // arg0: object (continuation)
+ sigBuilder.AppendElementType(ELEMENT_TYPE_BYREF); // arg1: ref byte (result location)
+ sigBuilder.AppendElementType(ELEMENT_TYPE_U1);
+
+ DWORD cbStubSig;
+ PVOID pStubSig = sigBuilder.GetSignature(&cbStubSig);
+
+ MethodDesc* pStubMD = ILStubCache::CreateR2RBackedILStub(
+ pMD->GetLoaderAllocator(),
+ pStubMT,
+ stubEntryPoint,
+ DynamicMethodDesc::StubAsyncResume,
+ (PCCOR_SIGNATURE)pStubSig,
+ cbStubSig,
+ FALSE,
+ &amTracker);
+
+ amTracker.SuppressRelease();
+
+ // Register the stub's entry point so GC can find it during stack walks
+ m_pCompositeInfo->SetMethodDescForEntryPointInNativeImage(stubEntryPoint, pStubMD);
+ }
+ else
+ {
+ pEntryPoint = (PCODE)NULL;
+ goto done;
+ }
+ }
+
#ifdef PROFILING_SUPPORTED
{
BEGIN_PROFILER_CALLBACK(CORProfilerTrackCacheSearches());
diff --git a/src/coreclr/vm/readytoruninfo.h b/src/coreclr/vm/readytoruninfo.h
index 9f58e81c736c93..57f2fea7d4613f 100644
--- a/src/coreclr/vm/readytoruninfo.h
+++ b/src/coreclr/vm/readytoruninfo.h
@@ -339,6 +339,10 @@ class ReadyToRunInfo
PTR_MethodDesc GetMethodDescForEntryPointInNativeImage(PCODE entryPoint);
void SetMethodDescForEntryPointInNativeImage(PCODE entryPoint, PTR_MethodDesc methodDesc);
+ // Looks up the R2R entry point for the resumption stub corresponding to the given async variant method.
+ // Returns NULL if not found.
+ PCODE LookupResumptionStubEntryPoint(MethodDesc* pAsyncVariantMD, PrepareCodeConfig* pConfig, BOOL fFixups, uint* pRuntimeFunctionIndex);
+
PTR_ReadyToRunCoreInfo GetComponentInfo() { return dac_cast(&m_component); }
friend struct ::cdac_data;
diff --git a/src/coreclr/vm/stackwalk.cpp b/src/coreclr/vm/stackwalk.cpp
index ed5de14ac839f5..9d5ea10b91ced7 100644
--- a/src/coreclr/vm/stackwalk.cpp
+++ b/src/coreclr/vm/stackwalk.cpp
@@ -505,9 +505,11 @@ PCODE Thread::VirtualUnwindCallFrame(T_CONTEXT* pContext,
ARM_ONLY((DWORD*))(&uImageBaseFromOS),
NULL);
- // Note that he address returned from the OS is different from the one we have computed
+ // Note that the address returned from the OS is different from the one we have computed
// when unwind info is registered using RtlAddGrowableFunctionTable. Compare RUNTIME_FUNCTION content.
- _ASSERTE( (uImageBase == uImageBaseFromOS) && (memcmp(pFunctionEntry, pFunctionEntryFromOS, sizeof(RUNTIME_FUNCTION)) == 0) );
+ // pFunctionEntryFromOS can be NULL for async methods in R2R images where unwind info may not be
+ // registered with the OS.
+ _ASSERTE( (pFunctionEntryFromOS == NULL) || ((uImageBase == uImageBaseFromOS) && (memcmp(pFunctionEntry, pFunctionEntryFromOS, sizeof(RUNTIME_FUNCTION)) == 0)) );
#endif // _DEBUG && !TARGET_UNIX
}
diff --git a/src/libraries/Directory.Build.targets b/src/libraries/Directory.Build.targets
index 4bcd8251decdab..947dbfc3e71a66 100644
--- a/src/libraries/Directory.Build.targets
+++ b/src/libraries/Directory.Build.targets
@@ -127,6 +127,12 @@
'$(IsGeneratorProject)' != 'true'">true
+
+
+ true
+ $(Features);runtime-async=on
+
+