diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/ILBodyFixupSignature.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/ILBodyFixupSignature.cs index 45b39e8de77d72..c6e8281e0d3613 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/ILBodyFixupSignature.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/ILBodyFixupSignature.cs @@ -13,17 +13,42 @@ using Internal.CorConstants; using Internal.JitInterface; +using ILCompiler.ReadyToRun.TypeSystem; + namespace ILCompiler.DependencyAnalysis.ReadyToRun { + /// + /// This fixup instructs the runtime to validate that the IL found at runtime matches the hash of the IL computed at compile time. + /// + /// + /// The fixup encodes two distinct pieces of information: + /// + /// The IL body hash, computed from the (the underlying EcmaMethod whose metadata + /// contains the IL). This is derived from _signatureMethod via GetPrimaryMethodDesc(). + /// The method identity (_signatureMethod), encoded in the fixup signature. The runtime decodes this + /// back to a MethodDesc via ZapSig::DecodeMethod and reads the IL at that method's RVA to compare against + /// the hash. + /// + /// For most methods, _signatureMethod is already the EcmaMethod. For runtime-async methods, the JIT inlines + /// an AsyncMethodVariant. The EcmaMethod for AsyncMethodVariant can be retrieved with GetPrimaryMethodDesc(). + /// public class ILBodyFixupSignature : Signature, IEquatable { private readonly ReadyToRunFixupKind _fixupKind; - private readonly EcmaMethod _method; - public ILBodyFixupSignature(ReadyToRunFixupKind fixupKind, EcmaMethod ecmaMethod) + /// The method identity encoded in the fixup signature that the runtime decodes to locate the IL at its RVA. + private readonly MethodDesc _signatureMethod; + + /// The underlying EcmaMethod whose IL body from metadata is hashed for the fixup validation. + private EcmaMethod ILMethod => (EcmaMethod)_signatureMethod.GetPrimaryMethodDesc(); + + public ILBodyFixupSignature(ReadyToRunFixupKind fixupKind, MethodDesc signatureMethod) { + Debug.Assert(signatureMethod.IsTypicalMethodDefinition); + Debug.Assert(!signatureMethod.IsCompilerGeneratedILBodyForAsync()); + Debug.Assert(signatureMethod.GetPrimaryMethodDesc() is EcmaMethod); _fixupKind = fixupKind; - _method = ecmaMethod; + _signatureMethod = signatureMethod; } public override int ClassCode => 308579267; @@ -44,10 +69,11 @@ public static void NotifyComplete(NodeFactory factory, List object.ReferenceEquals(other, this); 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 49c964cd0149da..ab07c61ce11c58 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 @@ -60,7 +60,7 @@ public override ObjectData GetData(NodeFactory factory, bool relocsOnly = false) if (relocsOnly) return new ObjectData(Array.Empty(), Array.Empty(), 1, new ISymbolDefinitionNode[] { this }); - Dictionary> inlineeToInliners = new Dictionary>(); + Dictionary> inlineeToInliners = new Dictionary>(); // Build a map from inlinee to the list of inliners // We are only interested in the generic definitions of these. @@ -72,11 +72,6 @@ public override ObjectData GetData(NodeFactory factory, bool relocsOnly = false) continue; } MethodDesc inliner = methodNode.Method; - if (inliner.IsCompilerGeneratedILBodyForAsync()) - { - // Async thunks are restricted from inlining. https://github.com/dotnet/runtime/issues/124665 - continue; - } EcmaMethod inlinerDefinition = (EcmaMethod)inliner.GetPrimaryMethodDesc().GetTypicalMethodDefinition(); if (inlinerDefinition.IsNonVersionable()) @@ -93,7 +88,7 @@ public override ObjectData GetData(NodeFactory factory, bool relocsOnly = false) foreach (MethodDesc inlinee in inlinees) { MethodDesc inlineeDefinition = inlinee.GetTypicalMethodDefinition(); - if (!(inlineeDefinition is EcmaMethod ecmaInlineeDefinition)) + if (!(inlineeDefinition is EcmaMethod or AsyncMethodVariant)) { // We don't record non-ECMA methods because they don't have tokens that // diagnostic tools could reason about anyway. @@ -114,7 +109,7 @@ public override ObjectData GetData(NodeFactory factory, bool relocsOnly = false) { if (!inlinerReportAllVersionsWithInlinee) { - // We'll won't report this method + // We won't report this method continue; } } @@ -123,17 +118,17 @@ public override ObjectData GetData(NodeFactory factory, bool relocsOnly = false) Debug.Assert(factory.CompilationModuleGroup.CrossModuleInlineable(inlineeDefinition)); if (_inlineInfoType != InfoType.CrossModuleInliningForCrossModuleDataOnly) { - // We'll won't report this method + // We won't report this method continue; } } - if (!inlineeToInliners.TryGetValue(ecmaInlineeDefinition, out HashSet inliners)) + if (!inlineeToInliners.TryGetValue(inlineeDefinition, out HashSet inliners)) { - inliners = new HashSet(); - inlineeToInliners.Add(ecmaInlineeDefinition, inliners); + inliners = new HashSet(); + inlineeToInliners.Add(inlineeDefinition, inliners); } - inliners.Add((EcmaMethod)inlinerDefinition); + inliners.Add(inlinerDefinition); } } @@ -146,8 +141,9 @@ public override ObjectData GetData(NodeFactory factory, bool relocsOnly = false) foreach (var inlineeWithInliners in inlineeToInliners) { - EcmaMethod inlinee = inlineeWithInliners.Key; - int inlineeRid = MetadataTokens.GetRowNumber(inlinee.Handle); + MethodDesc inlinee = inlineeWithInliners.Key; + EcmaMethod ecmaInlinee = (EcmaMethod)inlinee.GetPrimaryMethodDesc(); + int inlineeRid = MetadataTokens.GetRowNumber(ecmaInlinee.Handle); int hashCode; if (AllowCrossModuleInlines) @@ -158,7 +154,7 @@ public override ObjectData GetData(NodeFactory factory, bool relocsOnly = false) else { // InliningInfo2 format - hashCode = VersionResilientHashCode.ModuleNameHashCode(inlinee.Module); + hashCode = VersionResilientHashCode.ModuleNameHashCode(ecmaInlinee.Module); hashCode ^= inlineeRid; } @@ -173,14 +169,19 @@ public override ObjectData GetData(NodeFactory factory, bool relocsOnly = false) // Followed by inliner RIDs deltas with flag in the lowest bit // - if flag is set, followed by module ID Debug.Assert(_module != null); - bool isForeignInlinee = inlinee.Module != _module; + bool isForeignInlinee = ecmaInlinee.Module != _module; sig.Append(new UnsignedConstant((uint)(inlineeRid << 1 | (isForeignInlinee ? 1 : 0)))); if (isForeignInlinee) { - sig.Append(new UnsignedConstant((uint)factory.ManifestMetadataTable.ModuleToIndex(inlinee.Module))); + sig.Append(new UnsignedConstant((uint)factory.ManifestMetadataTable.ModuleToIndex(ecmaInlinee.Module))); } - List sortedInliners = new List(inlineeWithInliners.Value); + // We're only concerned with metadata here, so we can convert all to EcmaMethod and lose info about AsyncVariant vs Task-Returning + List sortedInliners = new List(inlineeWithInliners.Value.Count); + foreach (var inliner in inlineeWithInliners.Value) + { + sortedInliners.Add((EcmaMethod)inliner.GetPrimaryMethodDesc()); + } sortedInliners.MergeSort((a, b) => { if (a == b) @@ -302,7 +303,7 @@ public override ObjectData GetData(NodeFactory factory, bool relocsOnly = false) } else { - indexOfInlinee = (uint)MetadataTokens.GetRowNumber(inlinee.Handle); + indexOfInlinee = (uint)MetadataTokens.GetRowNumber(ecmaInlinee.Handle); } encodedInlinee = indexOfInlinee << (int)ReadyToRunCrossModuleInlineFlags.CrossModuleInlinerIndexShift; @@ -315,7 +316,7 @@ public override ObjectData GetData(NodeFactory factory, bool relocsOnly = false) sig.Append(new UnsignedConstant(encodedInlinee)); if (crossModuleMultiModuleFormat && !isCrossModuleInlinee) - sig.Append(new UnsignedConstant((uint)factory.ManifestMetadataTable.ModuleToIndex(inlinee.Module))); + sig.Append(new UnsignedConstant((uint)factory.ManifestMetadataTable.ModuleToIndex(ecmaInlinee.Module))); int inlinerIndex = 0; if (crossModuleInlinerCount > 0) @@ -339,7 +340,7 @@ public override ObjectData GetData(NodeFactory factory, bool relocsOnly = false) uint inlinerRid = (uint)MetadataTokens.GetRowNumber(inliner.Handle); uint ridDelta = inlinerRid - baseRid; baseRid = inlinerRid; - bool isForeignInliner = inliner.Module != inlinee.Module; + bool isForeignInliner = inliner.Module != ecmaInlinee.Module; Debug.Assert(!isForeignInliner || crossModuleMultiModuleFormat); if (crossModuleMultiModuleFormat) diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRunCodegenNodeFactory.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRunCodegenNodeFactory.cs index 88278fc10f707f..6e0d6535e341c5 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRunCodegenNodeFactory.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRunCodegenNodeFactory.cs @@ -644,11 +644,12 @@ public VirtualResolutionFixupSignature VirtualResolutionFixupSignature(ReadyToRu private struct ILBodyFixupSignatureFixupKey : IEquatable { public readonly ReadyToRunFixupKind FixupKind; - public readonly EcmaMethod Method; + public readonly MethodDesc Method; - public ILBodyFixupSignatureFixupKey(ReadyToRunFixupKind fixupKind, EcmaMethod method) + public ILBodyFixupSignatureFixupKey(ReadyToRunFixupKind fixupKind, MethodDesc method) { FixupKind = fixupKind; + Debug.Assert(method.IsTypicalMethodDefinition); Method = method; } public bool Equals(ILBodyFixupSignatureFixupKey other) => FixupKind == other.FixupKind && Method.Equals(other.Method); @@ -660,7 +661,7 @@ public ILBodyFixupSignatureFixupKey(ReadyToRunFixupKind fixupKind, EcmaMethod me private NodeCache _ilBodySignatures = new NodeCache((key) => new ILBodyFixupSignature(key.FixupKind, key.Method)); - public ILBodyFixupSignature ILBodyFixupSignature(ReadyToRunFixupKind fixupKind, EcmaMethod method) + public ILBodyFixupSignature ILBodyFixupSignature(ReadyToRunFixupKind fixupKind, MethodDesc method) { return _ilBodySignatures.GetOrAdd(new ILBodyFixupSignatureFixupKey(fixupKind, method)); } 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 4cf1f416a2a2ce..56afd326b88b95 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRunSymbolNodeFactory.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRunSymbolNodeFactory.cs @@ -9,6 +9,7 @@ using Internal.TypeSystem.Ecma; using Internal.ReadyToRunConstants; using Internal.Text; +using System.Diagnostics; namespace ILCompiler.DependencyAnalysis { @@ -507,8 +508,9 @@ public Import CheckVirtualFunctionOverride(MethodWithToken declMethod, TypeDesc } private NodeCache _ilBodyFixupsCache; - public Import CheckILBodyFixupSignature(EcmaMethod method) + public Import CheckILBodyFixupSignature(MethodDesc method) { + Debug.Assert(method.IsTypicalMethodDefinition); return _ilBodyFixupsCache.GetOrAdd(_codegenNodeFactory.ILBodyFixupSignature( _verifyTypeAndFieldLayout ? ReadyToRunFixupKind.Verify_IL_Body : ReadyToRunFixupKind.Check_IL_Body, method)); diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunCodegenCompilation.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunCodegenCompilation.cs index d254986e09d2a3..487072488bd146 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunCodegenCompilation.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunCodegenCompilation.cs @@ -109,12 +109,6 @@ public bool CanInline(MethodDesc caller, MethodDesc callee) } } - // TODO: Enable async inlining. https://github.com/dotnet/runtime/issues/124665 - if (callee.IsAsyncThunk() || callee.IsAsyncCall()) - { - return false; - } - _nodeFactory.DetectGenericCycles(caller, callee); return NodeFactory.CompilationModuleGroup.CanInline(caller, callee); @@ -655,14 +649,19 @@ public bool IsInheritanceChainLayoutFixedInCurrentVersionBubble(TypeDesc type) // The _finishedFirstCompilationRunInPhase2 variable works in concert some checking to ensure that we don't violate any of this model private bool _finishedFirstCompilationRunInPhase2 = false; - public void PrepareForCompilationRetry(MethodWithGCInfo methodToBeRecompiled, IEnumerable methodsThatNeedILBodies) + public void PrepareForCompilationRetry(MethodWithGCInfo methodToBeRecompiled, IEnumerable methodsThatNeedILBodies) { lock (_methodsToRecompile) { _methodsToRecompile.Add(methodToBeRecompiled); if (methodsThatNeedILBodies != null) + { foreach (var method in methodsThatNeedILBodies) + { + Debug.Assert(method.IsMethodDefinition); _methodsWhichNeedMutableILBodies.Add(method); + } + } } } diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunCompilationModuleGroupBase.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunCompilationModuleGroupBase.cs index 96f78ba0309247..7d3b30db219454 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunCompilationModuleGroupBase.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunCompilationModuleGroupBase.cs @@ -520,10 +520,6 @@ 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()) - return false; - // Defined in corelib MetadataType owningMetadataType = method.OwningType.GetTypeDefinition() as MetadataType; if (owningMetadataType == null) diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/IL/ReadyToRunILProvider.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/IL/ReadyToRunILProvider.cs index 5460cad265c0d6..690123979a0742 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/IL/ReadyToRunILProvider.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/IL/ReadyToRunILProvider.cs @@ -135,24 +135,38 @@ public void CreateCrossModuleInlineableTokensForILBody(MethodDesc method) Debug.Assert(_manifestMutableModule != null); var wrappedMethodIL = new ManifestModuleWrappedMethodIL(); - if (method.IsAsync) + // Check IsAsyncVariant() before IsAsync because AsyncMethodVariant delegates IsAsync to its target, + // so both would be true. The variant needs the async thunk IL, not the task-returning thunk. + if (method.IsAsyncVariant()) { - Debug.Assert(NeedsTaskReturningThunk(method)); - if (!wrappedMethodIL.Initialize(_manifestMutableModule, GetMethodILForAsyncMethod(method), (EcmaMethod)method, false)) + var amv = (AsyncMethodVariant)method; + if (NeedsAsyncThunk(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 - // the compilation, which is version safe. - wrappedMethodIL = null; + if (!wrappedMethodIL.Initialize(_manifestMutableModule, + AsyncThunkILEmitter.EmitAsyncMethodThunk(method, method.GetTargetOfAsyncVariant()), + method, + false)) + { + wrappedMethodIL = null; + } + } + else + { + // The async variant uses real ECMA IL (method has MethodImplAttributes.Async flag). + // Wrap the AsyncEcmaMethodIL for cross-module token translation. + if (!wrappedMethodIL.Initialize(_manifestMutableModule, + new AsyncEcmaMethodIL(amv, EcmaMethodIL.Create(amv.Target)), + method, + false)) + { + wrappedMethodIL = null; + } } } - else if (method.IsAsyncVariant()) + else if (method.IsAsync) { - Debug.Assert(NeedsAsyncThunk(method)); - if (!wrappedMethodIL.Initialize(_manifestMutableModule, - AsyncThunkILEmitter.EmitAsyncMethodThunk(method, method.GetTargetOfAsyncVariant()), - 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 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 cb670ba2db0cbe..0be908211636f7 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/JitInterface/CorInfoImpl.ReadyToRun.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/JitInterface/CorInfoImpl.ReadyToRun.cs @@ -474,7 +474,7 @@ unsafe partial class CorInfoImpl private HashSet _inlinedMethods; private UnboxingMethodDescFactory _unboxingThunkFactory = new UnboxingMethodDescFactory(); private List _precodeFixups; - private List _ilBodiesNeeded; + private List _ilBodiesNeeded; private Dictionary _preInitedTypes = new Dictionary(); private HashSet _synthesizedPgoDependencies; public bool HasColdCode { get; private set; } @@ -816,13 +816,15 @@ public void CompileMethod(MethodWithGCInfo methodCodeNodeNeedingCode, Logger log return; } - if (MethodBeingCompiled.GetTypicalMethodDefinition() is EcmaMethod ecmaMethod) + var typicalDef = MethodBeingCompiled.GetTypicalMethodDefinition(); + if (typicalDef is EcmaMethod or AsyncMethodVariant) { - if ((methodIL.GetMethodILScopeDefinition() is IEcmaMethodIL && _compilation.SymbolNodeFactory.VerifyTypeAndFieldLayout && ecmaMethod.Module == ecmaMethod.Context.SystemModule) || + var ecmaMethod = (EcmaMethod)typicalDef.GetPrimaryMethodDesc(); + if ((methodIL.GetMethodILScopeDefinition() is IEcmaMethodIL && _compilation.SymbolNodeFactory.VerifyTypeAndFieldLayout && ecmaMethod.Module == typicalDef.Context.SystemModule) || (!_compilation.NodeFactory.CompilationModuleGroup.VersionsWithMethodBody(MethodBeingCompiled) && _compilation.NodeFactory.CompilationModuleGroup.CrossModuleInlineable(MethodBeingCompiled))) { - ISymbolNode ilBodyNode = _compilation.SymbolNodeFactory.CheckILBodyFixupSignature((EcmaMethod)MethodBeingCompiled.GetTypicalMethodDefinition()); + ISymbolNode ilBodyNode = _compilation.SymbolNodeFactory.CheckILBodyFixupSignature(typicalDef); AddPrecodeFixup(ilBodyNode); } @@ -845,8 +847,8 @@ public void CompileMethod(MethodWithGCInfo methodCodeNodeNeedingCode, Logger log } else { - _ilBodiesNeeded = _ilBodiesNeeded ?? new List(); - _ilBodiesNeeded.Add(ecmaMethod); + _ilBodiesNeeded = _ilBodiesNeeded ?? new List(); + _ilBodiesNeeded.Add(typicalDef); } } } @@ -3333,38 +3335,29 @@ private void reportInliningDecision(CORINFO_METHOD_STRUCT_* inlinerHnd, CORINFO_ var typicalMethod = inlinee.GetTypicalMethodDefinition(); - if ((typicalMethod.IsAsyncVariant() || typicalMethod.IsAsync || typicalMethod.IsCompilerGeneratedILBodyForAsync()) && !_compilation.CompilationModuleGroup.VersionsWithMethodBody(typicalMethod)) - { - // TODO: fix this restriction in runtime async. https://github.com/dotnet/runtime/issues/124665 - // 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 - // yet. Currently, we'll correctly trigger the _ilBodiesNeeded logic below, but we also need to avoid triggering the ILBodyFixupSignature for the async thunks, but we ALSO need to make - // sure we generate the CheckILBodyFixupSignature for the actual runtime-async body in which case I think the typicalMethod will be an AsyncVariantMethod, which doesn't appear - // to be handled here. This check is here in the place where I believe we actually would behave incorrectly, but we also have a check in CrossModuleInlineable which disallows - // the cross module inline of async methods currently. - throw new Exception("Inlining async methods is not supported in ReadyToRun compilation."); - } - MethodIL methodIL = _compilation.GetMethodIL(typicalMethod); if (methodIL is ILStubMethodIL ilStubMethodIL && ilStubMethodIL.StubILHasGeneratedTokens) { // If a method is implemented by an IL Stub, then we may need to defer creation of the IL body that // we can really emit into the final file. - _ilBodiesNeeded = _ilBodiesNeeded ?? new List(); - _ilBodiesNeeded.Add((EcmaMethod)typicalMethod); + _ilBodiesNeeded = _ilBodiesNeeded ?? new List(); + Debug.Assert(typicalMethod.IsMethodDefinition); + _ilBodiesNeeded.Add(typicalMethod); } - else if (!_compilation.CompilationModuleGroup.VersionsWithMethodBody(typicalMethod) && - typicalMethod is EcmaMethod ecmaMethod) + else if (!_compilation.CompilationModuleGroup.VersionsWithMethodBody(typicalMethod) + && typicalMethod.GetPrimaryMethodDesc().GetTypicalMethodDefinition() is EcmaMethod ecmaMethod) { - // TODO, when we implement the async variant logic we'll need to detect generating the ILBodyFixupSignature here - Debug.Assert(_compilation.CompilationModuleGroup.CrossModuleInlineable(typicalMethod) || - _compilation.CompilationModuleGroup.IsNonVersionableWithILTokensThatDoNotNeedTranslation(typicalMethod)); - bool needsTokenTranslation = !_compilation.CompilationModuleGroup.IsNonVersionableWithILTokensThatDoNotNeedTranslation(typicalMethod); + Debug.Assert(_compilation.CompilationModuleGroup.CrossModuleInlineable(ecmaMethod) || + _compilation.CompilationModuleGroup.IsNonVersionableWithILTokensThatDoNotNeedTranslation(ecmaMethod)); + bool needsTokenTranslation = !_compilation.CompilationModuleGroup.IsNonVersionableWithILTokensThatDoNotNeedTranslation(ecmaMethod); if (needsTokenTranslation) { // This can happen if the method is marked as NonVersionable if there are tokens of interest // or if we are working with a full cross module inline - ISymbolNode ilBodyNode = _compilation.SymbolNodeFactory.CheckILBodyFixupSignature(ecmaMethod); + // The CheckILBodyFixupSignature will use the exact IL in metadata, not what is used by the ReadyToRunILProvider, + // which can be different for runtime-async methods (the ILProvider returns the Task-returning thunk for runtime-async EcmaMethods). + ISymbolNode ilBodyNode = _compilation.SymbolNodeFactory.CheckILBodyFixupSignature(typicalMethod); AddPrecodeFixup(ilBodyNode); } @@ -3374,11 +3367,15 @@ private void reportInliningDecision(CORINFO_METHOD_STRUCT_* inlinerHnd, CORINFO_ // tokens that are useable in compilation, record that information, and once the multi-threaded portion // of the build finishes, it will then compute the IL bodies for those methods, then run the compilation again. - if (needsTokenTranslation && !(methodIL is IMethodTokensAreUseableInCompilation) && methodIL is EcmaMethodIL) + if (needsTokenTranslation && !(methodIL is IMethodTokensAreUseableInCompilation) + && (methodIL is EcmaMethodIL || methodIL is ReadyToRunILProvider.AsyncEcmaMethodIL)) { - // We may have already acquired the right type of MethodIL here, or be working with a method that is an IL Intrinsic - _ilBodiesNeeded = _ilBodiesNeeded ?? new List(); - _ilBodiesNeeded.Add(ecmaMethod); + // We may have already acquired the right type of MethodIL here, or be working with a method that is an IL Intrinsic. + // Add the typicalMethod (which may be an AsyncMethodVariant) so that + // CreateCrossModuleInlineableTokensForILBody stores under the correct key. + _ilBodiesNeeded = _ilBodiesNeeded ?? new List(); + Debug.Assert(typicalMethod.IsMethodDefinition); + _ilBodiesNeeded.Add(typicalMethod); } } } diff --git a/src/tests/readytorun/async-inline-thunks/RuntimeAsyncMethodGenerationAttribute.cs b/src/tests/readytorun/async-inline-thunks/RuntimeAsyncMethodGenerationAttribute.cs new file mode 100644 index 00000000000000..e481c3bcefd748 --- /dev/null +++ b/src/tests/readytorun/async-inline-thunks/RuntimeAsyncMethodGenerationAttribute.cs @@ -0,0 +1,10 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace System.Runtime.CompilerServices; + +[AttributeUsage(AttributeTargets.Method)] +internal class RuntimeAsyncMethodGenerationAttribute(bool runtimeAsync) : Attribute +{ + public bool RuntimeAsync { get; } = runtimeAsync; +} diff --git a/src/tests/readytorun/async-inline-thunks/async-inline-thunks.cs b/src/tests/readytorun/async-inline-thunks/async-inline-thunks.cs new file mode 100644 index 00000000000000..be22f5244fa384 --- /dev/null +++ b/src/tests/readytorun/async-inline-thunks/async-inline-thunks.cs @@ -0,0 +1,155 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Runtime.CompilerServices; +using System.Threading.Tasks; + +public class InlineThunks +{ + static bool s_failed; + + static void Check(string testName, int expected, int actual) + { + if (expected != actual) + { + Console.WriteLine($" FAILED: {testName} — expected {expected}, got {actual}"); + s_failed = true; + } + } + + static void CheckNoThrow(string testName, Action action) + { + try + { + action(); + } + catch (Exception ex) + { + Console.WriteLine($" FAILED: {testName} — threw {ex.GetType().Name}: {ex.Message}"); + s_failed = true; + } + } + + public static int Main() + { + // Task-returning thunk inlining (non-async caller → async callee) + Check("TaskReturningThunk", 20, CallAsyncAndGetResult(10).GetAwaiter().GetResult()); + CheckNoThrow("TaskReturningThunkVoid", () => CallAsyncVoid().GetAwaiter().GetResult()); + Check("ValueTaskReturningThunk", 10, CallAsyncValueTask(5).GetAwaiter().GetResult()); + + // Async variant inlining (async caller → async callee) + Check("AsyncVariant", 14, AsyncCallerOfSmallAsync(7).GetAwaiter().GetResult()); + CheckNoThrow("AsyncVariantVoid", () => AsyncCallerOfSmallAsyncVoid().GetAwaiter().GetResult()); + Check("AsyncVariantValueTask", 6, AsyncCallerOfSmallValueTaskAsync(3).GetAwaiter().GetResult()); + + // Non-runtime-async (traditional state machine) inlining + Check("NonRuntimeAsyncNotAwaited", 8, CallNonRuntimeAsync(4).GetAwaiter().GetResult()); + Check("NonRuntimeAsyncAwaited", 12, AwaitNonRuntimeAsync(6).GetAwaiter().GetResult()); + Check("NonRuntimeValueTaskNotAwaited", 18, CallNonRuntimeValueTaskAsync(9).GetAwaiter().GetResult()); + Check("NonRuntimeValueTaskAwaited", 22, AwaitNonRuntimeValueTaskAsync(11).GetAwaiter().GetResult()); + + // Cross-module async inlining (helper.dll) + Check("CrossModuleAsyncVariant", 15, CrossModuleAsyncCaller(5).GetAwaiter().GetResult()); + CheckNoThrow("CrossModuleAsyncVariantVoid", () => CrossModuleAsyncCallerVoid().GetAwaiter().GetResult()); + Check("CrossModuleAsyncVariantValueTask", 12, CrossModuleAsyncCallerValueTask(4).GetAwaiter().GetResult()); + Check("CrossModuleTaskReturningThunk", 24, CrossModuleCallAsync(8).GetAwaiter().GetResult()); + + // Cross-module sync baseline + Check("CrossModuleSyncBaseline", 21, CrossModuleSyncCaller(7)); + + if (!s_failed) + Console.WriteLine("PASSED"); + else + Console.WriteLine("FAILED"); + + return s_failed ? 1 : 100; + } + + // --- Task-returning thunk inlining targets --- + + [MethodImpl(MethodImplOptions.NoInlining)] + private static Task CallAsyncAndGetResult(int x) => SmallAsyncForThunk(x); + + [MethodImpl(MethodImplOptions.NoInlining)] + private static Task CallAsyncVoid() => SmallAsyncVoidForThunk(); + + [MethodImpl(MethodImplOptions.NoInlining)] + private static ValueTask CallAsyncValueTask(int x) => SmallValueTaskAsyncForThunk(x); + + // --- Async variant inlining targets --- + + [MethodImpl(MethodImplOptions.NoInlining)] + private static async Task AsyncCallerOfSmallAsync(int x) => await SmallAsyncForVariant(x); + + [MethodImpl(MethodImplOptions.NoInlining)] + private static async Task AsyncCallerOfSmallAsyncVoid() => await SmallAsyncVoidForVariant(); + + [MethodImpl(MethodImplOptions.NoInlining)] + private static async Task AsyncCallerOfSmallValueTaskAsync(int x) => await SmallValueTaskAsyncForVariant(x); + + // --- Non-runtime-async callers --- + + [MethodImpl(MethodImplOptions.NoInlining)] + private static Task CallNonRuntimeAsync(int x) => SmallNonRuntimeAsync(x); + + [MethodImpl(MethodImplOptions.NoInlining)] + private static async Task AwaitNonRuntimeAsync(int x) => await SmallNonRuntimeAsync(x); + + [MethodImpl(MethodImplOptions.NoInlining)] + private static ValueTask CallNonRuntimeValueTaskAsync(int x) => SmallNonRuntimeValueTaskAsync(x); + + [MethodImpl(MethodImplOptions.NoInlining)] + private static async Task AwaitNonRuntimeValueTaskAsync(int x) => await SmallNonRuntimeValueTaskAsync(x); + + // --- Small async methods (runtime-async, for thunk tests) --- + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static async Task SmallAsyncForThunk(int x) => x * 2; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static async Task SmallAsyncVoidForThunk() { } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static async ValueTask SmallValueTaskAsyncForThunk(int x) => x * 2; + + // --- Small async methods (runtime-async, for variant tests) --- + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static async Task SmallAsyncForVariant(int x) => x * 2; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static async Task SmallAsyncVoidForVariant() { } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static async ValueTask SmallValueTaskAsyncForVariant(int x) => x * 2; + + // --- Small async methods (non-runtime-async / traditional state machine) --- + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [RuntimeAsyncMethodGeneration(false)] + private static async Task SmallNonRuntimeAsync(int x) => x * 2; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [RuntimeAsyncMethodGeneration(false)] + private static async ValueTask SmallNonRuntimeValueTaskAsync(int x) => x * 2; + + // --- Cross-module async callers --- + + [MethodImpl(MethodImplOptions.NoInlining)] + private static async Task CrossModuleAsyncCaller(int x) => await CrossModuleHelper.SmallCrossModuleAsync(x); + + [MethodImpl(MethodImplOptions.NoInlining)] + private static async Task CrossModuleAsyncCallerVoid() => await CrossModuleHelper.SmallCrossModuleAsyncVoid(); + + [MethodImpl(MethodImplOptions.NoInlining)] + private static async Task CrossModuleAsyncCallerValueTask(int x) => await CrossModuleHelper.SmallCrossModuleValueTaskAsync(x); + + [MethodImpl(MethodImplOptions.NoInlining)] + private static Task CrossModuleCallAsync(int x) => CrossModuleHelper.SmallCrossModuleAsync(x); + + // --- Cross-module sync baseline --- + + [MethodImpl(MethodImplOptions.NoInlining)] + private static int CrossModuleSyncCaller(int x) => CrossModuleHelper.SmallCrossModuleSync(x); +} diff --git a/src/tests/readytorun/async-inline-thunks/async-inline-thunks.csproj b/src/tests/readytorun/async-inline-thunks/async-inline-thunks.csproj new file mode 100644 index 00000000000000..52eb3c331b841c --- /dev/null +++ b/src/tests/readytorun/async-inline-thunks/async-inline-thunks.csproj @@ -0,0 +1,105 @@ + + + true + false + false + true + $(Features);runtime-async=on + + true + + true + + + + + + + + + + NUL +copy /y async-inline-thunks.dll IL_DLLS\async-inline-thunks.dll +if not exist IL_DLLS\async-inline-thunks.dll ( + echo FAILED to copy async-inline-thunks.dll to IL_DLLS + exit /b 1 +) + +REM Crossgen2 the helper library +%Core_Root%\crossgen2\crossgen2.exe -O -r:%Core_Root%\*.dll -r:%25CD%\IL_DLLS\async-inline-thunks.dll -o:helper.dll helper\helper\helper.dll + +set CrossGenStatus=!ERRORLEVEL! +IF NOT !CrossGenStatus!==0 ( + ECHO Crossgen of helper.dll failed with exitcode - !CrossGenStatus! + Exit /b 1 +) + +REM Crossgen2 the main test with cross-module inlining from helper +%Core_Root%\crossgen2\crossgen2.exe -O --opt-cross-module:helper -r:%Core_Root%\*.dll -r:helper\helper\helper.dll -o:async-inline-thunks.dll IL_DLLS\async-inline-thunks.dll + +set CrossGenStatus=!ERRORLEVEL! +IF NOT !CrossGenStatus!==0 ( + ECHO Crossgen of async-inline-thunks.dll failed with exitcode - !CrossGenStatus! + Exit /b 1 +) + +endlocal +]]> + + + + + + $(OutputPath)/../helper/helper/ + $(OutputPath)/helper/helper + + + + + diff --git a/src/tests/readytorun/async-inline-thunks/helper/helper.cs b/src/tests/readytorun/async-inline-thunks/helper/helper.cs new file mode 100644 index 00000000000000..b2da9411c7ff74 --- /dev/null +++ b/src/tests/readytorun/async-inline-thunks/helper/helper.cs @@ -0,0 +1,31 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Runtime.CompilerServices; +using System.Threading.Tasks; + +public static class CrossModuleHelper +{ + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static async Task SmallCrossModuleAsync(int x) + { + return x * 3; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static async Task SmallCrossModuleAsyncVoid() + { + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static async ValueTask SmallCrossModuleValueTaskAsync(int x) + { + return x * 3; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int SmallCrossModuleSync(int x) + { + return x * 3; + } +} diff --git a/src/tests/readytorun/async-inline-thunks/helper/helper.csproj b/src/tests/readytorun/async-inline-thunks/helper/helper.csproj new file mode 100644 index 00000000000000..59894cc101f610 --- /dev/null +++ b/src/tests/readytorun/async-inline-thunks/helper/helper.csproj @@ -0,0 +1,10 @@ + + + Library + $(Features);runtime-async=on + + + + + +