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..e0593908252c1b 100644 --- a/src/coreclr/tools/Common/Compiler/AsyncMethodVariant.cs +++ b/src/coreclr/tools/Common/Compiler/AsyncMethodVariant.cs @@ -79,9 +79,9 @@ public static bool IsAsyncVariant(this MethodDesc method) return method.GetTypicalMethodDefinition() is AsyncMethodVariant; } - public static bool IsAsyncThunk(this MethodDesc method) + public static bool IsCompilerGeneratedILBodyForAsync(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 f82c3914a4cf57..f3ef73fbfe79ef 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 @@ -120,7 +121,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); @@ -1384,8 +1385,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); @@ -3865,7 +3868,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.Compiler/Compiler/StackTraceEmissionPolicy.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/StackTraceEmissionPolicy.cs index 0b61b624b25236..a060668887fd2a 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/StackTraceEmissionPolicy.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/StackTraceEmissionPolicy.cs @@ -42,7 +42,7 @@ public override MethodStackTraceVisibilityFlags GetMethodVisibility(MethodDesc m if (method.HasCustomAttribute("System.Diagnostics", "StackTraceHiddenAttribute") || (method.OwningType is MetadataType mdType && mdType.HasCustomAttribute("System.Diagnostics", "StackTraceHiddenAttribute")) || (method is Internal.IL.Stubs.ILStubMethod) - || method.IsAsyncThunk()) // see MethodDesc::IsDiagnosticsHidden() in src/coreclr/vm/method.inl + || method.IsCompilerGeneratedILBodyForAsync()) // see MethodDesc::IsDiagnosticsHidden() in src/coreclr/vm/method.inl { result |= MethodStackTraceVisibilityFlags.IsHidden; } 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..88971a048f2845 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.IsCompilerGeneratedILBodyForAsync()) + { + // 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..64ea2dd340aa05 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,7 +53,7 @@ public override void AppendMangledName(NameMangler nameMangler, Utf8StringBuilde public static byte[] BuildSignatureForMethodDefinedInModule(MethodDesc method, NodeFactory factory) { - EcmaMethod typicalMethod = (EcmaMethod)method.GetTypicalMethodDefinition(); + EcmaMethod typicalMethod = (EcmaMethod)method.GetPrimaryMethodDesc().GetTypicalMethodDefinition(); ModuleToken moduleToken; if (factory.CompilationModuleGroup.VersionsWithMethodBody(typicalMethod)) @@ -63,7 +63,7 @@ public static byte[] BuildSignatureForMethodDefinedInModule(MethodDesc method, N else { MutableModule manifestMetadata = factory.ManifestMetadataTable._mutableModule; - var handle = manifestMetadata.TryGetExistingEntityHandle(method.GetTypicalMethodDefinition()); + var handle = manifestMetadata.TryGetExistingEntityHandle(typicalMethod); 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..3d072e9bd4bd99 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,8 @@ public override ObjectData GetData(NodeFactory factory, bool relocsOnly = false) MethodWithToken method = _method; - if (factory.CompilationModuleGroup.VersionsWithMethodBody(method.Method) && !method.Method.IsAsyncVariant()) + // If the method can be uniquely identified by a single token in the version bubble, use that instead of the full MethodSpec. + if (factory.CompilationModuleGroup.VersionsWithMethodBody(method.Method) && method.Method.IsPrimaryMethodDesc()) { if (method.Token.TokenType == CorTokenType.mdtMethodSpec) { 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..03b9b1e9864996 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 { @@ -100,7 +101,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)) { @@ -160,7 +161,6 @@ public ModuleToken GetModuleTokenForField(FieldDesc field, bool allowDynamically } } - public void AddModuleTokenForMethod(MethodDesc method, ModuleToken token) { if (token.TokenType == CorTokenType.mdtMethodSpec) @@ -332,6 +332,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..25e52271feac00 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 @@ -121,7 +121,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/ReadyToRunCodegenCompilation.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunCodegenCompilation.cs index 82c54c7b8d9a95..78b2254f3c85e1 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.IsCompilerGeneratedILBodyForAsync() || 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/ReadyToRunCompilationModuleGroupBase.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunCompilationModuleGroupBase.cs index 96f78ba0309247..35f6928130bf0e 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunCompilationModuleGroupBase.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunCompilationModuleGroupBase.cs @@ -521,7 +521,7 @@ private bool CrossModuleInlineableInternal(MethodDesc method) private bool CrossModuleInlineableUncached(MethodDesc method) { // Async thunks and variants cannot currently be inlined cross module - if (method.IsAsyncVariant() || method.IsAsync || method.IsAsyncThunk()) + if (method.IsAsyncVariant() || method.IsAsync || method.IsCompilerGeneratedILBodyForAsync()) return false; // Defined in corelib diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunTableManager.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunTableManager.cs index 25bec37f74584a..1777280bdeb244 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()).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..8945e6a076c872 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,50 @@ 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)) + Debug.Assert(NeedsTaskReturningThunk(method)); + 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()) + { + Debug.Assert(NeedsAsyncThunk(method)); + 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 +179,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 +192,50 @@ 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) { + Debug.Assert(method.IsTypicalMethodDefinition); + 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) + { + Debug.Assert(method.IsTypicalMethodDefinition); + 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 +260,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) ? + AsyncThunkILEmitter.EmitAsyncMethodThunk(amv, amv.Target) + : new AsyncEcmaMethodIL(amv, EcmaMethodIL.Create(amv.Target)); } else if (method is MethodForInstantiatedType || method is InstantiatedMethod) { @@ -247,6 +291,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 +311,7 @@ class ManifestModuleWrappedMethodIL : MethodIL, IEcmaMethodIL, IMethodTokensAreU { int _maxStack; bool _isInitLocals; - EcmaMethod _owningMethod; + MethodDesc _owningMethod; ILExceptionRegion[] _exceptionRegions; byte[] _ilBytes; LocalVariableDefinition[] _locals; @@ -276,7 +326,7 @@ 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) { HashSet methodsWhichCannotHaveAsyncVariants = null; _methodsWithAsyncVariants = null; @@ -288,7 +338,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 +367,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 +462,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 1cff95dd437131..b5f21e86363ac0 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..a6d1bbe1adf7bb 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,26 @@ 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); + } 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); + } 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); + } case CorInfoHelpFunc.CORINFO_HELP_INITCLASS: case CorInfoHelpFunc.CORINFO_HELP_INITINSTCLASS: @@ -1320,7 +1323,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 +1372,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 +1383,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 +1442,7 @@ 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(); + resultMethod = resultMethod.GetPrimaryMethodDesc().GetTypicalMethodDefinition(); if (!_compilation.NodeFactory.CompilationModuleGroup.VersionsWithType(resultMethod.OwningType)) { @@ -1462,24 +1467,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 +1476,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) @@ -1528,7 +1540,7 @@ private ObjectNode.ObjectData EncodeEHInfo() CORINFO_EH_CLAUSE_FLAGS flags = clause.Flags; uint classTokenOrOffset = clause.ClassTokenOrOffset; if (flags == CORINFO_EH_CLAUSE_FLAGS.CORINFO_EH_CLAUSE_NONE - && MethodBeingCompiled.IsAsyncThunk() + && MethodBeingCompiled.IsCompilerGeneratedILBodyForAsync() && ((TypeDesc)ResolveTokenInScope(_compilation.GetMethodIL(MethodBeingCompiled), MethodBeingCompiled, (mdToken)classTokenOrOffset)).IsWellKnownType(WellKnownType.Exception)) { flags |= CORINFO_EH_CLAUSE_FLAGS.CORINFO_EH_CLAUSE_R2R_SYSTEM_EXCEPTION; @@ -1934,7 +1946,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,7 +2511,7 @@ 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; @@ -3260,7 +3272,7 @@ private void reportInliningDecision(CORINFO_METHOD_STRUCT_* inlinerHnd, CORINFO_ var typicalMethod = inlinee.GetTypicalMethodDefinition(); - if ((typicalMethod.IsAsyncVariant() || typicalMethod.IsAsync || typicalMethod.IsAsyncThunk()) && !_compilation.CompilationModuleGroup.VersionsWithMethodBody(typicalMethod)) + if ((typicalMethod.IsAsyncVariant() || typicalMethod.IsAsync || typicalMethod.IsCompilerGeneratedILBodyForAsync()) && !_compilation.CompilationModuleGroup.VersionsWithMethodBody(typicalMethod)) { // TODO: fix this restriction in runtime async // Disable async methods in cross module inlines for now, we need to trigger the CheckILBodyFixupSignature in the right situations, and that hasn't been implemented 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 abdd36fc0931f8..e210159d18707b 100644 --- a/src/coreclr/vm/method.hpp +++ b/src/coreclr/vm/method.hpp @@ -3027,6 +3027,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..8f9446e127a110 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" @@ -1006,12 +1008,14 @@ ReadyToRunInfo::ReadyToRunInfo(Module * pModule, LoaderAllocator* pLoaderAllocat } } -static bool SigMatchesMethodDesc(MethodDesc* pMD, SigPointer &sig, ModuleBase * pModule) +// When matchResumptionStub is true, this matches only signatures with the +// ENCODE_METHOD_SIG_ResumptionStub flag set, matching against the underlying +// async variant method rather than a resumption stub MethodDesc. When false, +// it performs normal method signature matching including async/resumption flag checks. +static bool SigMatchesMethodDesc(MethodDesc* pMD, SigPointer &sig, ModuleBase * pModule, bool matchResumptionStub = false) { STANDARD_VM_CONTRACT; - _ASSERTE(!pMD->IsAsyncVariantMethod()); - ModuleBase *pOrigModule = pModule; ZapSig::Context zapSigContext(pModule, (void *)pModule, ZapSig::NormalTokens); ZapSig::Context * pZapSigContext = &zapSigContext; @@ -1019,11 +1023,19 @@ static bool SigMatchesMethodDesc(MethodDesc* pMD, SigPointer &sig, ModuleBase * uint32_t methodFlags; IfFailThrow(sig.GetData(&methodFlags)); + bool sigIsResumptionStub = (methodFlags & ENCODE_METHOD_SIG_ResumptionStub) != 0; + if (sigIsResumptionStub != matchResumptionStub) + return false; + + bool sigIsAsync = (methodFlags & ENCODE_METHOD_SIG_AsyncVariant) != 0; + if (sigIsAsync != pMD->IsAsyncVariantMethod()) + 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) + if (methodFlags & ENCODE_METHOD_SIG_UpdateContext) { uint32_t updatedModuleIndex; IfFailThrow(sig.GetData(&updatedModuleIndex)); @@ -1184,6 +1196,71 @@ PCODE ReadyToRunInfo::GetEntryPoint(MethodDesc * pMD, PrepareCodeConfig* pConfig bool printedStart = false; #endif + PCODE pEntryPoint = (PCODE)NULL; + pEntryPoint = GetEntryPoint(pMD, pConfig, fFixups, false); + // ResumptionStub for async variants. + + // 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 = GetEntryPoint(pMD, pConfig, fFixups, true); + if (stubEntryPoint != (PCODE)NULL) + { + // Create a DynamicMethodDesc wrapper for the R2R resumption stub + AllocMemTracker amTracker; + MethodTable* pStubMT = m_pModule->GetILStubCache()->GetOrCreateStubMethodTable(m_pModule); + + // Resumption stub signature: object(object, ref byte) + // This matches BuildResumptionStubSignature in jitinterface.cpp + static const BYTE s_resumptionStubSig[] = { + IMAGE_CEE_CS_CALLCONV_DEFAULT, // calling convention + 2, // 2 arguments + ELEMENT_TYPE_OBJECT, // return type: object (continuation) + ELEMENT_TYPE_OBJECT, // arg0: object (continuation) + ELEMENT_TYPE_BYREF, // arg1: ref byte (result location) + ELEMENT_TYPE_U1 + }; + + MethodDesc* pStubMD = ILStubCache::CreateR2RBackedILStub( + pMD->GetLoaderAllocator(), + pStubMT, + stubEntryPoint, + DynamicMethodDesc::StubAsyncResume, + (PCCOR_SIGNATURE)s_resumptionStubSig, + sizeof(s_resumptionStubSig), + FALSE, + &amTracker); + + amTracker.SuppressRelease(); + + // Register the stub's entry point so GC can find it during stack walks + m_pCompositeInfo->SetMethodDescForEntryPointInNativeImage(stubEntryPoint, pStubMD); + } + else + { + // If we cannot find the resumption stub, don't return an entry point for the async variant method + // TODO: This can cause valid async variant methods without an await to fail to resolve an entry point. + // We should consider other ways to ensure a resumption stub is found when expected, and allow async + // variants without awaits to be used without a resumption stub. + pEntryPoint = (PCODE)NULL; + } + } + + if (ETW_EVENT_ENABLED(MICROSOFT_WINDOWS_DOTNETRUNTIME_PROVIDER_DOTNET_Context, R2RGetEntryPoint)) + { + ETW::MethodLog::GetR2RGetEntryPoint(pMD, pEntryPoint); + } + return pEntryPoint; +} + +PCODE ReadyToRunInfo::GetEntryPoint(MethodDesc * pMD, PrepareCodeConfig* pConfig, BOOL fFixups, bool matchResumptionStub) +{ + STANDARD_VM_CONTRACT; + + _ASSERTE(matchResumptionStub ? pMD->IsAsyncVariantMethod() : true); + PCODE pEntryPoint = (PCODE)NULL; #ifdef PROFILING_SUPPORTED BOOL fShouldSearchCache = TRUE; @@ -1196,14 +1273,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; @@ -1215,7 +1290,7 @@ PCODE ReadyToRunInfo::GetEntryPoint(MethodDesc * pMD, PrepareCodeConfig* pConfig { PCCOR_SIGNATURE pBlob = (PCCOR_SIGNATURE)entryParser.GetBlob(); SigPointer sig(pBlob); - if (SigMatchesMethodDesc(pMD, sig, m_pModule)) + if (SigMatchesMethodDesc(pMD, sig, m_pModule, matchResumptionStub)) { // Get the updated SigPointer location, so we can calculate the size of the blob, // in order to skip the blob and find the entry point data. @@ -1302,10 +1377,6 @@ PCODE ReadyToRunInfo::GetEntryPoint(MethodDesc * pMD, PrepareCodeConfig* pConfig } done: - if (ETW_EVENT_ENABLED(MICROSOFT_WINDOWS_DOTNETRUNTIME_PROVIDER_DOTNET_Context, R2RGetEntryPoint)) - { - ETW::MethodLog::GetR2RGetEntryPoint(pMD, pEntryPoint); - } return pEntryPoint; } diff --git a/src/coreclr/vm/readytoruninfo.h b/src/coreclr/vm/readytoruninfo.h index 9f58e81c736c93..f5d9aac2973d00 100644 --- a/src/coreclr/vm/readytoruninfo.h +++ b/src/coreclr/vm/readytoruninfo.h @@ -336,6 +336,8 @@ class ReadyToRunInfo BOOL GetEnclosingToken(IMDInternalImport * pImport, ModuleBase *pModule1, mdToken mdType, mdToken * pEnclosingToken); BOOL CompareTypeNameOfTokens(mdToken mdToken1, IMDInternalImport * pImport1, ModuleBase *pModule1, mdToken mdToken2, IMDInternalImport * pImport2, ModuleBase *pModule2); + PCODE GetEntryPoint(MethodDesc * pMD, PrepareCodeConfig* pConfig, BOOL fFixups, bool matchResumptionStub); + PTR_MethodDesc GetMethodDescForEntryPointInNativeImage(PCODE entryPoint); void SetMethodDescForEntryPointInNativeImage(PCODE entryPoint, PTR_MethodDesc methodDesc); 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 + +