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 + +