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