diff --git a/src/coreclr/tools/Common/Compiler/AsyncMethodVariant.cs b/src/coreclr/tools/Common/Compiler/AsyncMethodVariant.cs index 8b68888e29e5f7..78b0c9c76b174b 100644 --- a/src/coreclr/tools/Common/Compiler/AsyncMethodVariant.cs +++ b/src/coreclr/tools/Common/Compiler/AsyncMethodVariant.cs @@ -171,5 +171,11 @@ public static MethodDesc GetTargetOfAsyncVariant(this MethodDesc method) Debug.Assert(method.IsAsyncVariant()); return ((CompilerTypeSystemContext)method.Context).GetTargetOfAsyncVariantMethod(method); } + + public static MethodDesc GetTargetOfReturnDroppingAsyncThunk(this MethodDesc method) + { + Debug.Assert(method.IsReturnDroppingAsyncThunk()); + return ((CompilerTypeSystemContext)method.Context).GetTargetOfReturnDroppingThunk(method); + } } } diff --git a/src/coreclr/tools/Common/Compiler/CompilerTypeSystemContext.Async.cs b/src/coreclr/tools/Common/Compiler/CompilerTypeSystemContext.Async.cs index e974d78ffb00ca..a1c8177e5a09d4 100644 --- a/src/coreclr/tools/Common/Compiler/CompilerTypeSystemContext.Async.cs +++ b/src/coreclr/tools/Common/Compiler/CompilerTypeSystemContext.Async.cs @@ -207,6 +207,26 @@ public MethodDesc GetAsyncVariantMethod(MethodDesc taskReturningMethod) return result; } + public MethodDesc GetTargetOfReturnDroppingThunk(MethodDesc returnDroppingThunk) + { + Debug.Assert(returnDroppingThunk.IsReturnDroppingAsyncThunk()); + var returnDroppingThunkDefinition = (ReturnDroppingAsyncThunk)returnDroppingThunk.GetTypicalMethodDefinition(); + MethodDesc result = returnDroppingThunkDefinition.AsyncVariantTarget; + + // If there are generics involved, we need to specialize + if (returnDroppingThunk != returnDroppingThunkDefinition) + { + TypeDesc owningType = returnDroppingThunk.OwningType; + if (owningType != returnDroppingThunkDefinition.OwningType) + result = GetMethodForInstantiatedType(result, (InstantiatedType)owningType); + + if (returnDroppingThunk.HasInstantiation && !returnDroppingThunk.IsMethodDefinition) + result = GetInstantiatedMethod(result, returnDroppingThunk.Instantiation); + } + + return result; + } + private sealed class AsyncVariantHashtable : LockFreeReaderHashtable { protected override int GetKeyHashCode(EcmaMethod key) => key.GetHashCode(); diff --git a/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs b/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs index dcc065e8303292..c26f42dcba91d9 100644 --- a/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs +++ b/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs @@ -1872,6 +1872,7 @@ private void resolveToken(ref CORINFO_RESOLVED_TOKEN pResolvedToken) else { recordToken = (_compilation.CompilationModuleGroup.VersionsWithType(owningType) || _compilation.CompilationModuleGroup.CrossModuleInlineableType(owningType)) && owningType is EcmaType; + recordToken &= methodIL.GetMethodILScopeDefinition() is IMethodTokensAreUseableInCompilation || methodIL.GetMethodILScopeDefinition() is IEcmaMethodIL; } #endif @@ -1884,7 +1885,8 @@ private void resolveToken(ref CORINFO_RESOLVED_TOKEN pResolvedToken) #if READYTORUN if (recordToken) { - ModuleToken methodModuleToken = HandleToModuleToken(ref pResolvedToken); + ModuleToken methodModuleToken = HandleToModuleToken(ref pResolvedToken, out bool strippedInstantiation); + Debug.Assert(!strippedInstantiation); var resolver = _compilation.NodeFactory.Resolver; resolver.AddModuleTokenForMethod(method, methodModuleToken); ValidateSafetyOfUsingTypeEquivalenceInSignature(method.Signature); @@ -1951,7 +1953,8 @@ private void resolveToken(ref CORINFO_RESOLVED_TOKEN pResolvedToken) #if READYTORUN if (recordToken) { - _compilation.NodeFactory.Resolver.AddModuleTokenForType(type, HandleToModuleToken(ref pResolvedToken)); + _compilation.NodeFactory.Resolver.AddModuleTokenForType(type, HandleToModuleToken(ref pResolvedToken, out bool strippedInstantiation)); + Debug.Assert(!strippedInstantiation); } #endif diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCases/CrossModuleInlining/AsyncInlineCallers.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCases/CrossModuleInlining/AsyncInlineCallers.cs new file mode 100644 index 00000000000000..50bde024a8ca56 --- /dev/null +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCases/CrossModuleInlining/AsyncInlineCallers.cs @@ -0,0 +1,44 @@ +// 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 AsyncInlineCallers +{ + [MethodImpl(MethodImplOptions.NoInlining)] + public static async Task CallReturnTaskNoAwait() + { + await AsyncInlineCandidatesLib.ReturnTaskNoAwait(); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static async Task CallReturnTaskPrimitiveNoAwait() + { + return await AsyncInlineCandidatesLib.ReturnTaskPrimitiveNoAwait(); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static async Task CallReturnTaskClassNoAwait() + { + return await AsyncInlineCandidatesLib.ReturnTaskClassNoAwait(); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static async Task CallReturnTaskWithAwait() + { + await AsyncInlineCandidatesLib.ReturnTaskWithAwait(); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static async Task CallReturnTaskPrimitiveWithAwait() + { + return await AsyncInlineCandidatesLib.ReturnTaskPrimitiveWithAwait(); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static async Task CallReturnTaskClassWithAwait() + { + return await AsyncInlineCandidatesLib.ReturnTaskClassWithAwait(); + } +} diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCases/CrossModuleInlining/Dependencies/AsyncInlineCandidatesLib.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCases/CrossModuleInlining/Dependencies/AsyncInlineCandidatesLib.cs new file mode 100644 index 00000000000000..17d31e2f8f16a8 --- /dev/null +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCases/CrossModuleInlining/Dependencies/AsyncInlineCandidatesLib.cs @@ -0,0 +1,43 @@ +// 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 AsyncInlineCandidatesLib +{ + // --- Awaitless variants: should be inlinable --- + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static async Task ReturnTaskNoAwait() + { + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static async Task ReturnTaskPrimitiveNoAwait() => 42; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static async Task ReturnTaskClassNoAwait() => "no_await"; + + // --- Variants containing an actual await: cannot be inlined by the JIT --- + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static async Task ReturnTaskWithAwait() + { + await Task.Yield(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static async Task ReturnTaskPrimitiveWithAwait() + { + await Task.Yield(); + return 42; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static async Task ReturnTaskClassWithAwait() + { + await Task.Yield(); + return "with_await"; + } +} diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCases/R2RTestSuites.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCases/R2RTestSuites.cs index 9e42292047dfe9..78bdb5535f9241 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCases/R2RTestSuites.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCases/R2RTestSuites.cs @@ -4,7 +4,6 @@ using System.Collections.Generic; using ILCompiler.ReadyToRun.Tests.TestCasesRunner; using ILCompiler.Reflection.ReadyToRun; -using Microsoft.DotNet.XUnitExtensions; using Xunit; using Xunit.Abstractions; @@ -49,10 +48,11 @@ [new CrossgenCompilation(basicCrossModuleInlining.AssemblyName, [cgInlineableLib static void Validate(ReadyToRunReader reader) { - R2RAssert.HasManifestRef(reader, "InlineableLib"); - R2RAssert.HasCrossModuleInlinedMethod(reader, "TestGetValue", "GetValue"); - R2RAssert.HasCrossModuleInlinedMethod(reader, "TestGetString", "GetString"); - R2RAssert.HasCrossModuleInliningInfo(reader); + string diag; + Assert.True(R2RAssert.HasManifestRef(reader, "InlineableLib", out diag), diag); + Assert.True(R2RAssert.HasCrossModuleInlinedMethod(reader, "TestGetValue", "GetValue", out diag), diag); + Assert.True(R2RAssert.HasCrossModuleInlinedMethod(reader, "TestGetString", "GetString", out diag), diag); + Assert.True(R2RAssert.HasCrossModuleInliningInfo(reader, out diag), diag); } } @@ -90,9 +90,10 @@ public void TransitiveReferences() { Validate = reader => { - R2RAssert.HasManifestRef(reader, "InlineableLibTransitive"); - R2RAssert.HasManifestRef(reader, "ExternalLib"); - R2RAssert.HasCrossModuleInlinedMethod(reader, "TestTransitiveValue", "GetExternalValue"); + string diag; + Assert.True(R2RAssert.HasManifestRef(reader, "InlineableLibTransitive", out diag), diag); + Assert.True(R2RAssert.HasManifestRef(reader, "ExternalLib", out diag), diag); + Assert.True(R2RAssert.HasCrossModuleInlinedMethod(reader, "TestTransitiveValue", "GetExternalValue", out diag), diag); }, }, ])); @@ -132,8 +133,9 @@ public void AsyncCrossModuleInlining() static void Validate(ReadyToRunReader reader) { - R2RAssert.HasManifestRef(reader, "AsyncInlineableLib"); - R2RAssert.HasCrossModuleInlinedMethod(reader, "TestAsyncInline", "GetValueAsync"); + string diag; + Assert.True(R2RAssert.HasManifestRef(reader, "AsyncInlineableLib", out diag), diag); + Assert.True(R2RAssert.HasCrossModuleInlinedMethod(reader, "TestAsyncInline", "GetValueAsync", out diag), diag); } } @@ -168,7 +170,8 @@ public void CompositeBasic() static void Validate(ReadyToRunReader reader) { - R2RAssert.HasManifestRef(reader, "CompositeLib"); + string diag; + Assert.True(R2RAssert.HasManifestRef(reader, "CompositeLib", out diag), diag); } } @@ -197,9 +200,10 @@ public void RuntimeAsyncMethodEmission() static void Validate(ReadyToRunReader reader) { - R2RAssert.HasAsyncVariant(reader, "SimpleAsyncMethod"); - R2RAssert.HasAsyncVariant(reader, "AsyncVoidReturn"); - R2RAssert.HasAsyncVariant(reader, "ValueTaskMethod"); + string diag; + Assert.True(R2RAssert.HasAsyncVariant(reader, "SimpleAsyncMethod", out diag), diag); + Assert.True(R2RAssert.HasAsyncVariant(reader, "AsyncVoidReturn", out diag), diag); + Assert.True(R2RAssert.HasAsyncVariant(reader, "ValueTaskMethod", out diag), diag); } } @@ -233,11 +237,12 @@ public void RuntimeAsyncContinuationLayout() static void Validate(ReadyToRunReader reader) { - R2RAssert.HasAsyncVariant(reader, "CaptureObjectAcrossAwait"); - R2RAssert.HasAsyncVariant(reader, "CaptureMultipleRefsAcrossAwait"); - R2RAssert.HasContinuationLayout(reader, "CaptureObjectAcrossAwait"); - R2RAssert.HasContinuationLayout(reader, "CaptureMultipleRefsAcrossAwait"); - R2RAssert.HasResumptionStubFixup(reader, "CaptureObjectAcrossAwait"); + string diag; + Assert.True(R2RAssert.HasAsyncVariant(reader, "CaptureObjectAcrossAwait", out diag), diag); + Assert.True(R2RAssert.HasAsyncVariant(reader, "CaptureMultipleRefsAcrossAwait", out diag), diag); + Assert.True(R2RAssert.HasContinuationLayout(reader, "CaptureObjectAcrossAwait", out diag), diag); + Assert.True(R2RAssert.HasContinuationLayout(reader, "CaptureMultipleRefsAcrossAwait", out diag), diag); + Assert.True(R2RAssert.HasResumptionStubFixup(reader, "CaptureObjectAcrossAwait", out diag), diag); } } @@ -270,7 +275,8 @@ public void RuntimeAsyncDevirtualize() static void Validate(ReadyToRunReader reader) { - R2RAssert.HasAsyncVariant(reader, "GetValueAsync"); + string diag; + Assert.True(R2RAssert.HasAsyncVariant(reader, "GetValueAsync", out diag), diag); } } @@ -303,8 +309,9 @@ public void RuntimeAsyncNoYield() static void Validate(ReadyToRunReader reader) { - R2RAssert.HasAsyncVariant(reader, "AsyncButNoAwait"); - R2RAssert.HasAsyncVariant(reader, "AsyncWithConditionalAwait"); + string diag; + Assert.True(R2RAssert.HasAsyncVariant(reader, "AsyncButNoAwait", out diag), diag); + Assert.True(R2RAssert.HasAsyncVariant(reader, "AsyncWithConditionalAwait", out diag), diag); } } @@ -356,8 +363,9 @@ public void RuntimeAsyncCrossModule() static void Validate(ReadyToRunReader reader) { - R2RAssert.HasManifestRef(reader, "AsyncDepLib"); - R2RAssert.HasAsyncVariant(reader, "CallCrossModuleAsync"); + string diag; + Assert.True(R2RAssert.HasManifestRef(reader, "AsyncDepLib", out diag), diag); + Assert.True(R2RAssert.HasAsyncVariant(reader, "CallCrossModuleAsync", out diag), diag); } } @@ -401,8 +409,109 @@ public void CompositeCrossModuleInlining() static void Validate(ReadyToRunReader reader) { - R2RAssert.HasManifestRef(reader, "InlineableLib"); - R2RAssert.HasInlinedMethod(reader, "TestGetValue", "GetValue"); + string diag; + Assert.True(R2RAssert.HasManifestRef(reader, "InlineableLib", out diag), diag); + Assert.True(R2RAssert.HasInlinedMethod(reader, "TestGetValue", "GetValue", out diag), diag); + } + } + + /// + /// Negative test: a composite image whose only inputs are the inlinee and the inliner + /// assemblies does NOT produce a CrossModuleInlineInfo section. CrossModuleInlineInfo only records + /// inlining where the inlinee module is outside the compiled image's version bubble + /// + [Fact] + public void CompositeDoesNotProduceCrossModuleInliningInfo() + { + var inlineableLib = new CompiledAssembly + { + AssemblyName = "InlineableLib", + SourceResourceNames = ["CrossModuleInlining/Dependencies/InlineableLib.cs"], + }; + var compositeMain = new CompiledAssembly + { + AssemblyName = nameof(CompositeDoesNotProduceCrossModuleInliningInfo), + SourceResourceNames = ["CrossModuleInlining/BasicInlining.cs"], + References = [inlineableLib] + }; + + new R2RTestRunner(_output).Run(new R2RTestCase( + nameof(CompositeDoesNotProduceCrossModuleInliningInfo), + [ + new(nameof(CompositeDoesNotProduceCrossModuleInliningInfo), + [ + new CrossgenAssembly(inlineableLib), + new CrossgenAssembly(compositeMain), + ]) + { + Options = [Crossgen2Option.Composite, Crossgen2Option.Optimize], + Validate = Validate, + }, + ])); + + static void Validate(ReadyToRunReader reader) + { + string diag; + // Inlining still happens in composite mode — recorded in InliningInfo2 — confirming + // the assertion below is meaningful (we are not just looking at a no-op compilation). + Assert.True(R2RAssert.HasInlinedMethod(reader, "TestGetValue", "GetValue", out diag), diag); + + // But no CrossModuleInlineInfo section/entries should be present in composite output. + Assert.False(R2RAssert.HasCrossModuleInliningInfo(reader, out diag), diag); + } + } + + /// + /// Positive complement to : + /// composite mode produces a CrossModuleInlineInfo section when an inlineable method + /// comes from an assembly outside the version bubble (passed as a Reference with + /// --opt-cross-module). + /// + [Fact] + public void CompositeProducesCrossModuleInliningInfoForExternalReference() + { + var inlineableLib = new CompiledAssembly + { + AssemblyName = "InlineableLib", + SourceResourceNames = ["CrossModuleInlining/Dependencies/InlineableLib.cs"], + }; + var compositeLib = new CompiledAssembly + { + AssemblyName = "CompositeLib", + SourceResourceNames = ["CrossModuleInlining/Dependencies/CompositeLib.cs"], + }; + var compositeMain = new CompiledAssembly + { + AssemblyName = nameof(CompositeProducesCrossModuleInliningInfoForExternalReference), + SourceResourceNames = ["CrossModuleInlining/BasicInlining.cs"], + References = [inlineableLib] + }; + + new R2RTestRunner(_output).Run(new R2RTestCase( + nameof(CompositeProducesCrossModuleInliningInfoForExternalReference), + [ + new(nameof(CompositeProducesCrossModuleInliningInfoForExternalReference), + [ + new CrossgenAssembly(inlineableLib) + { + Kind = Crossgen2InputKind.Reference, + Options = [Crossgen2AssemblyOption.CrossModuleOptimization], + }, + new CrossgenAssembly(compositeLib), + new CrossgenAssembly(compositeMain), + ]) + { + Options = [Crossgen2Option.Composite, Crossgen2Option.Optimize], + Validate = Validate, + }, + ])); + + static void Validate(ReadyToRunReader reader) + { + string diag; + Assert.True(R2RAssert.HasManifestRef(reader, "InlineableLib", out diag), diag); + Assert.True(R2RAssert.HasCrossModuleInlinedMethod(reader, "TestGetValue", "GetValue", out diag), diag); + Assert.True(R2RAssert.HasCrossModuleInliningInfo(reader, out diag), diag); } } @@ -410,7 +519,6 @@ static void Validate(ReadyToRunReader reader) /// Composite mode with runtime-async methods in both assemblies. /// Validates async variants exist in composite output. /// - [ActiveIssue("https://github.com/dotnet/runtime/issues/125337")] [Fact] public void CompositeAsync() { @@ -444,43 +552,105 @@ public void CompositeAsync() static void Validate(ReadyToRunReader reader) { - R2RAssert.HasManifestRef(reader, "AsyncCompositeLib"); - R2RAssert.HasAsyncVariant(reader, "CallCompositeAsync"); - R2RAssert.HasAsyncVariant(reader, "GetValueAsync"); + string diag; + Assert.True(R2RAssert.HasManifestRef(reader, "AsyncCompositeLib", out diag), diag); + Assert.True(R2RAssert.HasAsyncVariant(reader, "CallCompositeAsync", out diag), diag); + Assert.True(R2RAssert.HasAsyncVariant(reader, "GetValueAsync", out diag), diag); } } /// - /// The full intersection: composite + runtime-async + cross-module inlining. - /// Async methods from AsyncCompositeLib are inlined into CompositeAsyncMain - /// within a composite image, exercising MutableModule token encoding for - /// cross-module async continuation layouts. + /// Composite + runtime-async + intra-bubble inlining matrix test. + /// Verifies that, in composite mode, awaitless async candidates ARE inlined into + /// their callers. /// - [ActiveIssue("https://github.com/dotnet/runtime/issues/125337")] [Fact] - public void CompositeAsyncCrossModuleInlining() + public void CompositeAsyncInliningMatrix() { - var asyncCompositeLib = new CompiledAssembly + var asyncInlineCandidatesLib = new CompiledAssembly { - AssemblyName = "AsyncCompositeLib", - SourceResourceNames = ["CrossModuleInlining/Dependencies/AsyncCompositeLib.cs"], + AssemblyName = "AsyncInlineCandidatesLib", + SourceResourceNames = ["CrossModuleInlining/Dependencies/AsyncInlineCandidatesLib.cs"], Features = { RuntimeAsyncFeature }, }; - var compositeAsyncMain = new CompiledAssembly + var asyncInlineCallers = new CompiledAssembly { - AssemblyName = "CompositeAsyncMain", - SourceResourceNames = ["CrossModuleInlining/CompositeAsync.cs"], + AssemblyName = "AsyncInlineCallers", + SourceResourceNames = ["CrossModuleInlining/AsyncInlineCallers.cs"], Features = { RuntimeAsyncFeature }, - References = [asyncCompositeLib] + References = [asyncInlineCandidatesLib] }; new R2RTestRunner(_output).Run(new R2RTestCase( - nameof(CompositeAsyncCrossModuleInlining), + nameof(CompositeAsyncInliningMatrix), [ - new(nameof(CompositeAsyncCrossModuleInlining), + new(nameof(CompositeAsyncInliningMatrix), [ - new CrossgenAssembly(asyncCompositeLib), - new CrossgenAssembly(compositeAsyncMain), + new CrossgenAssembly(asyncInlineCandidatesLib), + new CrossgenAssembly(asyncInlineCallers), + ]) + { + Options = [Crossgen2Option.Composite, Crossgen2Option.Optimize], + Validate = Validate, + }, + ])); + + static void Validate(ReadyToRunReader reader) + { + string diag; + Assert.True(R2RAssert.HasManifestRef(reader, "AsyncInlineCandidatesLib", out diag), diag); + + // Awaitless async candidates: should be inlined into their callers. + Assert.True(R2RAssert.HasInlinedMethod(reader, "CallReturnTaskNoAwait", "ReturnTaskNoAwait", out diag), diag); + Assert.True(R2RAssert.HasInlinedMethod(reader, "CallReturnTaskPrimitiveNoAwait", "ReturnTaskPrimitiveNoAwait", out diag), diag); + Assert.True(R2RAssert.HasInlinedMethod(reader, "CallReturnTaskClassNoAwait", "ReturnTaskClassNoAwait", out diag), diag); + + // Async candidates that contain a real await: cannot be inlined by the JIT. + Assert.False(R2RAssert.HasInlinedMethod(reader, "CallReturnTaskWithAwait", "ReturnTaskWithAwait", out diag), diag); + Assert.False(R2RAssert.HasInlinedMethod(reader, "CallReturnTaskPrimitiveWithAwait", "ReturnTaskPrimitiveWithAwait", out diag), diag); + Assert.False(R2RAssert.HasInlinedMethod(reader, "CallReturnTaskClassWithAwait", "ReturnTaskClassWithAwait", out diag), diag); + } + } + + /// + /// Validate that async thunks with generic owning types are correctly emitted in composite mode. + /// Async thunks (and all "faux" method IL stubs) strip the instantiation away when constructing a MethodWithToken. + /// This is fine for the Method instantiation, but the Type instantiation needs to be tracked properly. + /// https://github.com/dotnet/runtime/pull/126904 added support for ensuring the OwningType signature modifier is emitted + /// for these methods. + /// + [Fact] + public void CompositeAsyncGenericTypes() + { + var asyncGenericTypeLib = new CompiledAssembly + { + AssemblyName = "AsyncGenericTypeLib", + SourceResourceNames = + [ + "RuntimeAsync/Dependencies/AsyncGenericTypeLib.cs", + "RuntimeAsync/RuntimeAsyncMethodGenerationAttribute.cs", + ], + Features = { RuntimeAsyncFeature }, + }; + var compositeAsyncGenericTypesMain = new CompiledAssembly + { + AssemblyName = "CompositeAsyncGenericTypesMain", + SourceResourceNames = + [ + "RuntimeAsync/CompositeAsyncGenericTypesMain.cs", + "RuntimeAsync/RuntimeAsyncMethodGenerationAttribute.cs", + ], + Features = { RuntimeAsyncFeature }, + References = [asyncGenericTypeLib] + }; + + new R2RTestRunner(_output).Run(new R2RTestCase( + nameof(CompositeAsyncGenericTypes), + [ + new(nameof(CompositeAsyncGenericTypes), + [ + new CrossgenAssembly(asyncGenericTypeLib), + new CrossgenAssembly(compositeAsyncGenericTypesMain), ]) { Options = [Crossgen2Option.Composite, Crossgen2Option.Optimize], @@ -490,10 +660,22 @@ public void CompositeAsyncCrossModuleInlining() static void Validate(ReadyToRunReader reader) { - R2RAssert.HasManifestRef(reader, "AsyncCompositeLib"); - R2RAssert.HasAsyncVariant(reader, "CallCompositeAsync"); - R2RAssert.HasInlinedMethod(reader, "CallCompositeAsync", "GetValueAsync"); - R2RAssert.HasContinuationLayout(reader, "CallCompositeAsync"); + string diag; + // Async thunks for the consumer's instantiated callers. + Assert.True(R2RAssert.HasAsyncVariant(reader, "CallGenericContainerInt", out diag), diag); + Assert.True(R2RAssert.HasAsyncVariant(reader, "CallGenericContainerString", out diag), diag); + Assert.True(R2RAssert.HasAsyncVariant(reader, "CallGenericMethodOnGenericTypeIntLong", out diag), diag); + Assert.True(R2RAssert.HasAsyncVariant(reader, "CallGenericMethodOnGenericTypeStringObject", out diag), diag); + + // Async thunks for the library's generic-type methods, asserted with their + // generic-arg instantiations to ensure we aren't matching only the open + // (unspecialized) method signature. Reference-type instantiations are shared + // through the canonical (__Canon) form, so the string consumer's calls also + // produce the __Canon variant rather than a separate entry. + Assert.True(R2RAssert.HasAsyncVariant(reader, "GenericContainer`1.GetValueAsync", out diag), diag); + Assert.True(R2RAssert.HasAsyncVariant(reader, "GenericContainer`1<__Canon>.GetValueAsync", out diag), diag); + Assert.True(R2RAssert.HasAsyncVariant(reader, "GenericContainer`1.CombineAsync", out diag), diag); + Assert.True(R2RAssert.HasAsyncVariant(reader, "GenericContainer`1<__Canon>.CombineAsync<__Canon>", out diag), diag); } } @@ -546,9 +728,10 @@ public void AsyncCrossModuleContinuation() static void Validate(ReadyToRunReader reader) { - R2RAssert.HasManifestRef(reader, "AsyncDepLibContinuation"); - R2RAssert.HasAsyncVariant(reader, "CallCrossModuleCaptureRef"); - R2RAssert.HasAsyncVariant(reader, "CallCrossModuleCaptureArray"); + string diag; + Assert.True(R2RAssert.HasManifestRef(reader, "AsyncDepLibContinuation", out diag), diag); + Assert.True(R2RAssert.HasAsyncVariant(reader, "CallCrossModuleCaptureRef", out diag), diag); + Assert.True(R2RAssert.HasAsyncVariant(reader, "CallCrossModuleCaptureArray", out diag), diag); } } @@ -589,7 +772,8 @@ public void MultiStepCompositeAndNonComposite() Options = [Crossgen2Option.Composite, Crossgen2Option.Optimize], Validate = reader => { - R2RAssert.HasManifestRef(reader, "MultiStepLibA"); + string diag; + Assert.True(R2RAssert.HasManifestRef(reader, "MultiStepLibA", out diag), diag); }, }, new("NonCompositeStep", @@ -604,8 +788,9 @@ public void MultiStepCompositeAndNonComposite() { Validate = reader => { - R2RAssert.HasManifestRef(reader, "MultiStepLibA"); - R2RAssert.HasCrossModuleInlinedMethod(reader, "GetValueFromLibA", "GetValue"); + string diag; + Assert.True(R2RAssert.HasManifestRef(reader, "MultiStepLibA", out diag), diag); + Assert.True(R2RAssert.HasCrossModuleInlinedMethod(reader, "GetValueFromLibA", "GetValue", out diag), diag); }, }, ])); @@ -619,7 +804,6 @@ public void MultiStepCompositeAndNonComposite() /// Composite + runtime-async + cross-module devirtualization. /// Interface defined in AsyncInterfaceLib, call sites in CompositeAsyncDevirtMain. /// - [ActiveIssue("https://github.com/dotnet/runtime/issues/125337")] [Fact] public void CompositeAsyncDevirtualize() { @@ -657,8 +841,9 @@ public void CompositeAsyncDevirtualize() static void Validate(ReadyToRunReader reader) { - R2RAssert.HasManifestRef(reader, "AsyncInterfaceLib"); - R2RAssert.HasAsyncVariant(reader, "CallOnSealed"); + string diag; + Assert.True(R2RAssert.HasManifestRef(reader, "AsyncInterfaceLib", out diag), diag); + Assert.True(R2RAssert.HasAsyncVariant(reader, "CallOnSealed", out diag), diag); } } @@ -704,8 +889,9 @@ public void CompositeTransitive() static void Validate(ReadyToRunReader reader) { - R2RAssert.HasManifestRef(reader, "InlineableLibTransitive"); - R2RAssert.HasManifestRef(reader, "ExternalLib"); + string diag; + Assert.True(R2RAssert.HasManifestRef(reader, "InlineableLibTransitive", out diag), diag); + Assert.True(R2RAssert.HasManifestRef(reader, "ExternalLib", out diag), diag); } } @@ -764,8 +950,9 @@ public void AsyncCrossModuleTransitive() static void Validate(ReadyToRunReader reader) { - R2RAssert.HasManifestRef(reader, "AsyncTransitiveLib"); - R2RAssert.HasAsyncVariant(reader, "CallTransitiveValueAsync"); + string diag; + Assert.True(R2RAssert.HasManifestRef(reader, "AsyncTransitiveLib", out diag), diag); + Assert.True(R2RAssert.HasAsyncVariant(reader, "CallTransitiveValueAsync", out diag), diag); } } @@ -773,7 +960,6 @@ static void Validate(ReadyToRunReader reader) /// Composite + runtime-async + transitive (3 assemblies). /// Full combination of composite, async, and transitive references. /// - [ActiveIssue("https://github.com/dotnet/runtime/issues/125337")] [Fact] public void CompositeAsyncTransitive() { @@ -822,8 +1008,9 @@ public void CompositeAsyncTransitive() static void Validate(ReadyToRunReader reader) { - R2RAssert.HasManifestRef(reader, "AsyncTransitiveLib"); - R2RAssert.HasAsyncVariant(reader, "CallTransitiveValueAsync"); + string diag; + Assert.True(R2RAssert.HasManifestRef(reader, "AsyncTransitiveLib", out diag), diag); + Assert.True(R2RAssert.HasAsyncVariant(reader, "CallTransitiveValueAsync", out diag), diag); } } @@ -832,22 +1019,20 @@ static void Validate(ReadyToRunReader reader) /// Step 1: Composite of async libs. Step 2: Non-composite consumer /// with cross-module inlining of async methods. /// - [ActiveIssue("https://github.com/dotnet/runtime/issues/125337")] [Fact] public void MultiStepCompositeAndNonCompositeAsync() { - var asyncCompositeLib = new CompiledAssembly + var asyncDepLib = new CompiledAssembly { - AssemblyName = "AsyncCompositeLib", - SourceResourceNames = ["CrossModuleInlining/Dependencies/AsyncCompositeLib.cs"], + AssemblyName = "AsyncDepLibContinuation", + SourceResourceNames = ["RuntimeAsync/Dependencies/AsyncDepLibContinuation.cs"], Features = { RuntimeAsyncFeature }, }; - var compositeAsyncMain = new CompiledAssembly + var asyncCompositeLib = new CompiledAssembly { - AssemblyName = "CompositeAsyncMain", - SourceResourceNames = ["CrossModuleInlining/CompositeAsync.cs"], + AssemblyName = "AsyncCompositeLib", + SourceResourceNames = ["CrossModuleInlining/Dependencies/AsyncCompositeLib.cs"], Features = { RuntimeAsyncFeature }, - References = [asyncCompositeLib] }; var asyncConsumer = new CompiledAssembly { @@ -858,7 +1043,7 @@ public void MultiStepCompositeAndNonCompositeAsync() "RuntimeAsync/RuntimeAsyncMethodGenerationAttribute.cs", ], Features = { RuntimeAsyncFeature }, - References = [asyncCompositeLib] + References = [asyncDepLib] }; new R2RTestRunner(_output).Run(new R2RTestCase( @@ -866,21 +1051,22 @@ public void MultiStepCompositeAndNonCompositeAsync() [ new("CompositeAsyncStep", [ + new CrossgenAssembly(asyncDepLib), new CrossgenAssembly(asyncCompositeLib), - new CrossgenAssembly(compositeAsyncMain), ]) { Options = [Crossgen2Option.Composite, Crossgen2Option.Optimize], Validate = reader => { - R2RAssert.HasManifestRef(reader, "AsyncCompositeLib"); - R2RAssert.HasAsyncVariant(reader, "CallCompositeAsync"); + string diag; + Assert.True(R2RAssert.HasManifestRef(reader, "AsyncDepLibContinuation", out diag), diag); + Assert.True(R2RAssert.HasAsyncVariant(reader, "CaptureRefAcrossAwait", out diag), diag); }, }, new("NonCompositeAsyncStep", [ new CrossgenAssembly(asyncConsumer), - new CrossgenAssembly(asyncCompositeLib) + new CrossgenAssembly(asyncDepLib) { Kind = Crossgen2InputKind.Reference, Options = [Crossgen2AssemblyOption.CrossModuleOptimization], @@ -889,8 +1075,9 @@ public void MultiStepCompositeAndNonCompositeAsync() { Validate = reader => { - R2RAssert.HasManifestRef(reader, "AsyncCompositeLib"); - R2RAssert.HasAsyncVariant(reader, "CallCrossModuleCaptureRef"); + string diag; + Assert.True(R2RAssert.HasManifestRef(reader, "AsyncDepLibContinuation", out diag), diag); + Assert.True(R2RAssert.HasAsyncVariant(reader, "CallCrossModuleCaptureRef", out diag), diag); }, }, ])); @@ -937,14 +1124,15 @@ public void CrossModuleGenericMultiInliner() static void Validate(ReadyToRunReader reader) { - R2RAssert.HasManifestRef(reader, "CrossModuleGenericLib"); - R2RAssert.HasCrossModuleInliningInfo(reader); + string diag; + Assert.True(R2RAssert.HasManifestRef(reader, "CrossModuleGenericLib", out diag), diag); + Assert.True(R2RAssert.HasCrossModuleInliningInfo(reader, out diag), diag); // Verify that GetValue has cross-module inliners from both GenericWrapperA and GenericWrapperB. // This exercises the cross-module inliner parsing path where indices // must be read as absolute values, not delta-accumulated, and validates // that the resolved method names match the expected inliners. - R2RAssert.HasCrossModuleInliners(reader, "GetValue", "GenericWrapperA", "GenericWrapperB"); + Assert.True(R2RAssert.HasCrossModuleInliners(reader, "GetValue", ["GenericWrapperA", "GenericWrapperB"], out diag), diag); } } } diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCases/RuntimeAsync/CompositeAsyncGenericTypesMain.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCases/RuntimeAsync/CompositeAsyncGenericTypesMain.cs new file mode 100644 index 00000000000000..e9d867f95d68e3 --- /dev/null +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCases/RuntimeAsync/CompositeAsyncGenericTypesMain.cs @@ -0,0 +1,37 @@ +// 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 static class CompositeAsyncGenericTypesMain +{ + [MethodImpl(MethodImplOptions.NoInlining)] + public static async Task CallGenericContainerInt() + { + var c = new GenericContainer(42); + return await c.GetValueAsync(); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static async Task CallGenericContainerString() + { + var c = new GenericContainer("hello"); + return await c.GetValueAsync(); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static async Task CallGenericMethodOnGenericTypeIntLong() + { + var c = new GenericContainer(7); + return await c.CombineAsync(11L); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static async Task CallGenericMethodOnGenericTypeStringObject() + { + var c = new GenericContainer("k"); + return await c.CombineAsync("v"); + } +} diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCases/RuntimeAsync/Dependencies/AsyncGenericTypeLib.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCases/RuntimeAsync/Dependencies/AsyncGenericTypeLib.cs new file mode 100644 index 00000000000000..a0cd495310aba7 --- /dev/null +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCases/RuntimeAsync/Dependencies/AsyncGenericTypeLib.cs @@ -0,0 +1,27 @@ +// 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 GenericContainer +{ + private readonly T _value; + + public GenericContainer(T value) => _value = value; + + [MethodImpl(MethodImplOptions.NoInlining)] + public async Task GetValueAsync() + { + await Task.Yield(); + return _value; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public async Task CombineAsync(U seed) + { + await Task.Yield(); + return $"{_value}+{seed}"; + } +} diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCasesRunner/R2RResultChecker.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCasesRunner/R2RResultChecker.cs index e3e47b450fd9b1..e27f50adf10338 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCasesRunner/R2RResultChecker.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCasesRunner/R2RResultChecker.cs @@ -35,9 +35,9 @@ public static List GetAllMethods(ReadyToRunReader reader) } /// - /// Asserts the R2R image contains a manifest or MSIL assembly reference with the given name. + /// Returns true if the R2R image contains a manifest or MSIL assembly reference with the given name. /// - public static void HasManifestRef(ReadyToRunReader reader, string assemblyName) + public static bool HasManifestRef(ReadyToRunReader reader, string assemblyName, out string diagnostic) { var allRefs = new HashSet(StringComparer.OrdinalIgnoreCase); @@ -55,19 +55,24 @@ public static void HasManifestRef(ReadyToRunReader reader, string assemblyName) foreach (var kvp in reader.ManifestReferenceAssemblies) allRefs.Add(kvp.Key); - Assert.True(allRefs.Contains(assemblyName), - $"Expected assembly reference '{assemblyName}' not found. " + - $"Found: [{string.Join(", ", allRefs.OrderBy(s => s))}]"); + bool found = allRefs.Contains(assemblyName); + diagnostic = found + ? $"Found manifest/MSIL ref '{assemblyName}'." + : $"Expected assembly reference '{assemblyName}' not found. " + + $"Found: [{string.Join(", ", allRefs.OrderBy(s => s))}]"; + return found; } /// - /// Asserts that the CrossModuleInlineInfo section records that - /// inlined , and that the inlinee is encoded as a cross-module - /// reference (ILBody import index, not a local MethodDef RID). + /// Returns true if the CrossModuleInlineInfo section records that + /// inlined and the inlinee is encoded as a cross-module + /// reference (ILBody import index, not a local MethodDef RID). If a pair matches but the + /// encoding is wrong, returns false with the mismatch described in . /// - public static void HasCrossModuleInlinedMethod(ReadyToRunReader reader, string inlinerMethodName, string inlineeMethodName) + public static bool HasCrossModuleInlinedMethod(ReadyToRunReader reader, string inlinerMethodName, string inlineeMethodName, out string diagnostic) { - var inliningInfo = GetCrossModuleInliningInfoSection(reader); + if (!TryGetCrossModuleInliningInfoSection(reader, out var inliningInfo, out diagnostic)) + return false; var allPairs = new List(); foreach (var (inlinerName, inlineeName, inlineeKind) in inliningInfo.GetInliningPairs()) @@ -77,31 +82,40 @@ public static void HasCrossModuleInlinedMethod(ReadyToRunReader reader, string i if (inlinerName.Contains(inlinerMethodName, StringComparison.OrdinalIgnoreCase) && inlineeName.Contains(inlineeMethodName, StringComparison.OrdinalIgnoreCase)) { - Assert.True(inlineeKind == CrossModuleInliningInfoSection.InlineeReferenceKind.CrossModule, + if (inlineeKind == CrossModuleInliningInfoSection.InlineeReferenceKind.CrossModule) + { + diagnostic = $"Found cross-module inlining '{inlinerName} → {inlineeName}'."; + return true; + } + + diagnostic = $"Found inlining pair '{inlinerName} → {inlineeName}' but the inlinee is not encoded " + - $"as a cross-module reference ({inlineeKind}). Expected ILBody import encoding."); - return; + $"as a cross-module reference ({inlineeKind}). Expected ILBody import encoding."; + return false; } } - Assert.Fail( + diagnostic = $"Expected cross-module inlining '{inlineeMethodName}' into '{inlinerMethodName}', but it was not found.\n" + - $"Recorded inlining pairs:\n {string.Join("\n ", allPairs)}"); + $"Recorded inlining pairs:\n {string.Join("\n ", allPairs)}"; + return false; } /// - /// Asserts that the CrossModuleInlineInfo section has an entry for an inlinee matching + /// Returns true if the CrossModuleInlineInfo section has an entry for an inlinee matching /// with cross-module inliners whose resolved names /// contain each of the . This validates that /// cross-module inliner indices (encoded as absolute ILBody import indices) resolve /// to the correct method names. /// - public static void HasCrossModuleInliners( + public static bool HasCrossModuleInliners( ReadyToRunReader reader, string inlineeMethodName, - params string[] expectedInlinerNames) + IEnumerable expectedInlinerNames, + out string diagnostic) { - var inliningInfo = GetCrossModuleInliningInfoSection(reader); + if (!TryGetCrossModuleInliningInfoSection(reader, out var inliningInfo, out diagnostic)) + return false; foreach (var entry in inliningInfo.GetEntries()) { @@ -118,30 +132,37 @@ public static void HasCrossModuleInliners( foreach (string expected in expectedInlinerNames) { - Assert.True( - crossModuleInlinerNames.Any(n => n.Contains(expected, StringComparison.OrdinalIgnoreCase)), - $"Inlinee '{inlineeName}': expected a cross-module inliner matching '{expected}' " + - $"but found only:\n {string.Join("\n ", crossModuleInlinerNames)}"); + if (!crossModuleInlinerNames.Any(n => n.Contains(expected, StringComparison.OrdinalIgnoreCase))) + { + diagnostic = + $"Inlinee '{inlineeName}': expected a cross-module inliner matching '{expected}' " + + $"but found only:\n {string.Join("\n ", crossModuleInlinerNames)}"; + return false; + } } - return; + diagnostic = + $"Inlinee '{inlineeName}' has all expected cross-module inliners: " + + $"[{string.Join(", ", expectedInlinerNames)}]"; + return true; } var allEntries = new List(); foreach (var (inlinerName, inlineeName, _) in inliningInfo.GetInliningPairs()) allEntries.Add($"{inlinerName} → {inlineeName}"); - Assert.Fail( + diagnostic = $"No CrossModuleInlineInfo entry found for inlinee matching '{inlineeMethodName}'.\n" + - $"All inlining pairs:\n {string.Join("\n ", allEntries)}"); + $"All inlining pairs:\n {string.Join("\n ", allEntries)}"; + return false; } /// - /// Asserts that any inlining info section (CrossModuleInlineInfo or InliningInfo2) records that - /// inlined . + /// Returns true if any inlining info section (CrossModuleInlineInfo or InliningInfo2) records + /// that inlined . /// Does not check whether the encoding is cross-module or local. /// - public static void HasInlinedMethod(ReadyToRunReader reader, string inlinerMethodName, string inlineeMethodName) + public static bool HasInlinedMethod(ReadyToRunReader reader, string inlinerMethodName, string inlineeMethodName, out string diagnostic) { var foundPairs = new List(); @@ -153,7 +174,8 @@ public static void HasInlinedMethod(ReadyToRunReader reader, string inlinerMetho if (inlinerName.Contains(inlinerMethodName, StringComparison.OrdinalIgnoreCase) && inlineeName.Contains(inlineeMethodName, StringComparison.OrdinalIgnoreCase)) { - return; + diagnostic = $"Found inlining '{inlinerName} -> {inlineeName}' in CrossModuleInlineInfo."; + return true; } foundPairs.Add($"[CXMI] {inlinerName} -> {inlineeName}"); } @@ -166,19 +188,18 @@ public static void HasInlinedMethod(ReadyToRunReader reader, string inlinerMetho if (inlinerName.Contains(inlinerMethodName, StringComparison.OrdinalIgnoreCase) && inlineeName.Contains(inlineeMethodName, StringComparison.OrdinalIgnoreCase)) { - return; + diagnostic = $"Found inlining '{inlinerName} -> {inlineeName}' in InliningInfo2."; + return true; } foundPairs.Add($"[II2] {inlinerName} -> {inlineeName}"); } } - string pairList = foundPairs.Count > 0 - ? string.Join("\n ", foundPairs) - : "(none)"; - - Assert.Fail( - $"Expected inlining '{inlineeMethodName}' into '{inlinerMethodName}', but it was not found.\n" + - $"Found inlining pairs:\n {pairList}"); + string pairList = foundPairs.Count > 0 ? string.Join("\n ", foundPairs) : "(none)"; + diagnostic = + $"Inlining '{inlineeMethodName}' into '{inlinerMethodName}' not found.\n" + + $"Inlining pairs in image:\n {pairList}"; + return false; } private static CrossModuleInliningInfoSection GetCrossModuleInliningInfoSection(ReadyToRunReader reader) @@ -194,6 +215,23 @@ private static CrossModuleInliningInfoSection GetCrossModuleInliningInfoSection( return new CrossModuleInliningInfoSection(reader, offset, endOffset); } + private static bool TryGetCrossModuleInliningInfoSection(ReadyToRunReader reader, out CrossModuleInliningInfoSection inliningInfo, out string diagnostic) + { + if (!reader.ReadyToRunHeader.Sections.TryGetValue( + ReadyToRunSectionType.CrossModuleInlineInfo, out ReadyToRunSection section)) + { + inliningInfo = null!; + diagnostic = "Expected CrossModuleInlineInfo section not found in R2R image."; + return false; + } + + int offset = reader.GetOffset(section.RelativeVirtualAddress); + int endOffset = offset + section.Size; + inliningInfo = new CrossModuleInliningInfoSection(reader, offset, endOffset); + diagnostic = ""; + return true; + } + private static IEnumerable GetAllInliningInfo2Sections(ReadyToRunReader reader) { // InliningInfo2 can appear in the global header @@ -226,95 +264,90 @@ private static IEnumerable GetAllInliningInfo2Sections(Rea } /// - /// Asserts the R2R image contains a CrossModuleInlineInfo section with at least one entry. + /// Returns true if the R2R image contains a CrossModuleInlineInfo section with at least one entry. /// - public static void HasCrossModuleInliningInfo(ReadyToRunReader reader) + public static bool HasCrossModuleInliningInfo(ReadyToRunReader reader, out string diagnostic) { - Assert.True( - reader.ReadyToRunHeader.Sections.TryGetValue( - ReadyToRunSectionType.CrossModuleInlineInfo, out ReadyToRunSection section), - "Expected CrossModuleInlineInfo section not found in R2R image."); + if (!TryGetCrossModuleInliningInfoSection(reader, out var inliningInfo, out diagnostic)) + return false; - int offset = reader.GetOffset(section.RelativeVirtualAddress); - int endOffset = offset + section.Size; - var inliningInfo = new CrossModuleInliningInfoSection(reader, offset, endOffset); string dump = inliningInfo.ToString(); + if (dump.Length == 0) + { + diagnostic = "CrossModuleInlineInfo section is present but contains no entries."; + return false; + } - Assert.True( - dump.Length > 0, - "CrossModuleInlineInfo section is present but contains no entries."); + diagnostic = $"CrossModuleInlineInfo contains entries:\n{dump}"; + return true; } /// - /// Asserts the R2R image contains an [ASYNC] variant entry whose signature contains the given method name. + /// Returns true if the R2R image contains an [ASYNC] variant entry whose signature contains the given method name. /// - public static void HasAsyncVariant(ReadyToRunReader reader, string methodName) + public static bool HasAsyncVariant(ReadyToRunReader reader, string methodName, out string diagnostic) { var asyncSigs = GetAllMethods(reader) .Where(m => m.SignatureString.Contains("[ASYNC]", StringComparison.OrdinalIgnoreCase)) .Select(m => m.SignatureString) .ToList(); - Assert.True( - asyncSigs.Any(s => s.Contains(methodName, StringComparison.OrdinalIgnoreCase)), - $"Expected [ASYNC] variant for '{methodName}' not found. " + - $"Async methods: [{string.Join(", ", asyncSigs)}]"); + bool found = asyncSigs.Any(s => s.Contains(methodName, StringComparison.OrdinalIgnoreCase)); + diagnostic = found + ? $"Found [ASYNC] variant for '{methodName}'." + : $"Expected [ASYNC] variant for '{methodName}' not found. " + + $"Async methods: [{string.Join(", ", asyncSigs)}]"; + return found; } /// - /// Asserts the R2R image contains a [RESUME] stub entry whose signature contains the given method name. + /// Returns true if the R2R image contains a [RESUME] stub entry whose signature contains the given method name. /// - public static void HasResumptionStub(ReadyToRunReader reader, string methodName) + public static bool HasResumptionStub(ReadyToRunReader reader, string methodName, out string diagnostic) { var resumeSigs = GetAllMethods(reader) .Where(m => m.SignatureString.Contains("[RESUME]", StringComparison.OrdinalIgnoreCase)) .Select(m => m.SignatureString) .ToList(); - Assert.True( - resumeSigs.Any(s => s.Contains(methodName, StringComparison.OrdinalIgnoreCase)), - $"Expected [RESUME] stub for '{methodName}' not found. " + - $"Resume methods: [{string.Join(", ", resumeSigs)}]"); + bool found = resumeSigs.Any(s => s.Contains(methodName, StringComparison.OrdinalIgnoreCase)); + diagnostic = found + ? $"Found [RESUME] stub for '{methodName}'." + : $"Expected [RESUME] stub for '{methodName}' not found. " + + $"Resume methods: [{string.Join(", ", resumeSigs)}]"; + return found; } /// - /// Asserts the R2R image contains at least one ContinuationLayout fixup. + /// Returns true if the R2R image contains at least one ContinuationLayout fixup. /// - public static void HasContinuationLayout(ReadyToRunReader reader) - { - HasFixupKind(reader, ReadyToRunFixupKind.ContinuationLayout); - } + public static bool HasContinuationLayout(ReadyToRunReader reader, out string diagnostic) + => HasFixupKind(reader, ReadyToRunFixupKind.ContinuationLayout, out diagnostic); /// - /// Asserts a method whose signature contains + /// Returns true if a method whose signature contains /// has at least one ContinuationLayout fixup. /// - public static void HasContinuationLayout(ReadyToRunReader reader, string methodName) - { - HasFixupKindOnMethod(reader, ReadyToRunFixupKind.ContinuationLayout, methodName); - } + public static bool HasContinuationLayout(ReadyToRunReader reader, string methodName, out string diagnostic) + => HasFixupKindOnMethod(reader, ReadyToRunFixupKind.ContinuationLayout, methodName, out diagnostic); /// - /// Asserts the R2R image contains at least one ResumptionStubEntryPoint fixup. + /// Returns true if the R2R image contains at least one ResumptionStubEntryPoint fixup. /// - public static void HasResumptionStubFixup(ReadyToRunReader reader) - { - HasFixupKind(reader, ReadyToRunFixupKind.ResumptionStubEntryPoint); - } + public static bool HasResumptionStubFixup(ReadyToRunReader reader, out string diagnostic) + => HasFixupKind(reader, ReadyToRunFixupKind.ResumptionStubEntryPoint, out diagnostic); /// - /// Asserts a method whose signature contains + /// Returns true if a method whose signature contains /// has at least one ResumptionStubEntryPoint fixup. /// - public static void HasResumptionStubFixup(ReadyToRunReader reader, string methodName) - { - HasFixupKindOnMethod(reader, ReadyToRunFixupKind.ResumptionStubEntryPoint, methodName); - } + public static bool HasResumptionStubFixup(ReadyToRunReader reader, string methodName, out string diagnostic) + => HasFixupKindOnMethod(reader, ReadyToRunFixupKind.ResumptionStubEntryPoint, methodName, out diagnostic); /// - /// Asserts the R2R image contains at least one fixup of the given kind. + /// Returns true if the R2R image contains at least one fixup of the given kind. /// - public static void HasFixupKind(ReadyToRunReader reader, ReadyToRunFixupKind kind) + public static bool HasFixupKind(ReadyToRunReader reader, ReadyToRunFixupKind kind, out string diagnostic) { var presentKinds = new HashSet(); foreach (var method in GetAllMethods(reader)) @@ -328,16 +361,19 @@ public static void HasFixupKind(ReadyToRunReader reader, ReadyToRunFixupKind kin } } - Assert.True(presentKinds.Contains(kind), - $"Expected fixup kind '{kind}' not found. " + - $"Present kinds: [{string.Join(", ", presentKinds)}]"); + bool found = presentKinds.Contains(kind); + diagnostic = found + ? $"Found fixup kind '{kind}'." + : $"Expected fixup kind '{kind}' not found. " + + $"Present kinds: [{string.Join(", ", presentKinds)}]"; + return found; } /// - /// Asserts a method whose signature contains + /// Returns true if a method whose signature contains /// has at least one fixup of the given kind. /// - public static void HasFixupKindOnMethod(ReadyToRunReader reader, ReadyToRunFixupKind kind, string methodName) + public static bool HasFixupKindOnMethod(ReadyToRunReader reader, ReadyToRunFixupKind kind, string methodName, out string diagnostic) { var methodsWithFixup = new List(); foreach (var method in GetAllMethods(reader)) @@ -359,13 +395,17 @@ public static void HasFixupKindOnMethod(ReadyToRunReader reader, ReadyToRunFixup { methodsWithFixup.Add(method.SignatureString); if (method.SignatureString.Contains(methodName, StringComparison.OrdinalIgnoreCase)) - return; + { + diagnostic = $"Found '{kind}' fixup on method matching '{methodName}'."; + return true; + } } } - Assert.Fail( + diagnostic = $"Expected fixup kind '{kind}' on method matching '{methodName}', but not found.\n" + - $"Methods with '{kind}' fixups: [{string.Join(", ", methodsWithFixup)}]"); + $"Methods with '{kind}' fixups: [{string.Join(", ", methodsWithFixup)}]"; + return false; } } 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 c6e8281e0d3613..18630355170110 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 @@ -103,7 +103,7 @@ public override ObjectData GetData(NodeFactory factory, bool relocsOnly = false) dataBuilder.EmitTypeSignature(typeRef, innerContext); } - MethodWithToken method = new MethodWithToken(_signatureMethod, moduleToken, null, unboxing: false, context: null); + MethodWithToken method = new MethodWithToken(_signatureMethod, moduleToken, null, unboxing: false, genericContextObject: null); dataBuilder.EmitMethodSignature(method, enforceDefEncoding: false, enforceOwningType: false, innerContext, false); } 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 ddbf57a2731e14..3a829774df604d 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 @@ -73,7 +73,7 @@ public static byte[] BuildSignatureForMethodDefinedInModule(MethodDesc method, N ArraySignatureBuilder signatureBuilder = new ArraySignatureBuilder(); signatureBuilder.EmitMethodSignature( - new MethodWithToken(method, moduleToken, constrainedType: null, unboxing: false, context: null), + new MethodWithToken(method, moduleToken, constrainedType: null, unboxing: false, genericContextObject: null), enforceDefEncoding: true, enforceOwningType: moduleToken.Module is EcmaModule ? factory.CompilationModuleGroup.EnforceOwningType((EcmaModule)moduleToken.Module) : true, factory.SignatureContext, diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/InstrumentationDataTableNode.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/InstrumentationDataTableNode.cs index 323661407e311d..9a152a33a61df2 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/InstrumentationDataTableNode.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/InstrumentationDataTableNode.cs @@ -147,7 +147,7 @@ private int MethodToInt(TypeSystemEntityOrUnknown handle) EcmaMethod typicalMethod = (EcmaMethod)handle.AsMethod.GetTypicalMethodDefinition(); ModuleToken moduleToken = new ModuleToken(typicalMethod.Module, typicalMethod.Handle); - MethodWithToken tok = new MethodWithToken(handle.AsMethod, moduleToken, constrainedType: null, unboxing: false, context: null); + MethodWithToken tok = new MethodWithToken(handle.AsMethod, moduleToken, constrainedType: null, unboxing: false, genericContextObject: null); Import methodHandleImport = (Import)_symbolFactory.CreateReadyToRunHelper(ReadyToRunHelperId.MethodHandle, tok); _imports.Add(methodHandleImport); 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 027ae0dbcc7140..1e8312e6c9cc26 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRunCodegenNodeFactory.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRunCodegenNodeFactory.cs @@ -511,7 +511,7 @@ public IEnumerable EnumerateCompiledMethods(EcmaModule moduleT EcmaModule module = ((EcmaMethod)method.GetTypicalMethodDefinition()).Module; ModuleToken moduleToken = Resolver.GetModuleTokenForMethod(method, allowDynamicallyCreatedReference: true, throwIfNotFound: true); - IMethodNode methodNodeDebug = MethodEntrypoint(new MethodWithToken(method, moduleToken, constrainedType: null, unboxing: false, context: null), false, false, false); + IMethodNode methodNodeDebug = MethodEntrypoint(new MethodWithToken(method, moduleToken, constrainedType: null, unboxing: false, genericContextObject: null), false, false, false); MethodWithGCInfo methodCodeNodeDebug = methodNodeDebug as MethodWithGCInfo; if (methodCodeNodeDebug == null && methodNodeDebug is DelayLoadMethodImport DelayLoadMethodImport) { diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ExternalReferenceTokenManager.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ExternalReferenceTokenManager.cs new file mode 100644 index 00000000000000..fd1fd6ae60784f --- /dev/null +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ExternalReferenceTokenManager.cs @@ -0,0 +1,183 @@ +// 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.Collections.Generic; +using System.Diagnostics; +using ILCompiler.DependencyAnalysis.ReadyToRun; +using ILCompiler.ReadyToRun.TypeSystem; +using Internal.TypeSystem; +using Internal.TypeSystem.Ecma; + +namespace ILCompiler.ReadyToRun +{ + /// + /// Handles the creation of IL tokens necessary for creating ReadyToRun signatures for types, methods, and fields not already present in the input modules within the version bubble. + /// + internal class ExternalReferenceTokenManager + { + private MutableModule _mutableModule; + private ModuleTokenResolver _tokenResolver; + + public ExternalReferenceTokenManager(MutableModule mutableModule, ModuleTokenResolver tokenResolver) + { + _mutableModule = mutableModule; + _tokenResolver = tokenResolver; + } + + /// + /// Ensures that all the tokens necessary for creating a ReadyToRun signature for the given entities are available. + /// If a token for an entity is not already resolvable from a module known to the , + /// a new token is added to the manifest . + /// + public void EnsureDefTokensAreAvailable(IEnumerable entities, ModuleDesc moduleForNewReferences, bool referencesAreForAsyncMethod) + { + Debug.Assert(_mutableModule.ModuleThatIsCurrentlyTheSourceOfNewReferences == null); + Debug.Assert(!_mutableModule.CreatingTokensForAsyncMethod); + _mutableModule.ModuleThatIsCurrentlyTheSourceOfNewReferences = moduleForNewReferences; + _mutableModule.CreatingTokensForAsyncMethod = referencesAreForAsyncMethod; + try + { + foreach (var entity in entities) + { + EnsureDefTokensAreAvailableInternal(entity); + } + } + finally + { + _mutableModule.ModuleThatIsCurrentlyTheSourceOfNewReferences = null; + _mutableModule.CreatingTokensForAsyncMethod = false; + } + } + + /// + /// Ensures that all the tokens necessary for creating a ReadyToRun signature for the given entity are available. + /// If a token for the entity is not already resolvable from a module known to the , + /// a new token is added to the manifest . + /// + public void EnsureDefTokensAreAvailable(TypeSystemEntity entity, ModuleDesc moduleForNewReferences, bool referencesAreForAsyncMethod) + { + Debug.Assert(_mutableModule.ModuleThatIsCurrentlyTheSourceOfNewReferences == null); + Debug.Assert(!_mutableModule.CreatingTokensForAsyncMethod); + _mutableModule.ModuleThatIsCurrentlyTheSourceOfNewReferences = moduleForNewReferences; + _mutableModule.CreatingTokensForAsyncMethod = referencesAreForAsyncMethod; + try + { + EnsureDefTokensAreAvailableInternal(entity); + } + finally + { + _mutableModule.ModuleThatIsCurrentlyTheSourceOfNewReferences = null; + _mutableModule.CreatingTokensForAsyncMethod = false; + } + } + + private void EnsureDefTokensAreAvailableInternal(TypeSystemEntity entity) + { + Debug.Assert(_mutableModule.ModuleThatIsCurrentlyTheSourceOfNewReferences != null); + switch (entity) + { + case TypeDesc typeDesc: + EnsureTypeDefTokensAreAvailableInVersionBubble(typeDesc); + break; + case MethodDesc methodDesc: + EnsureMethodDefTokensAreAvailableInVersionBubble(methodDesc); + break; + case FieldDesc fieldDesc: + EnsureFieldDefTokensAreAvailableInVersionBubble(fieldDesc); + break; + default: + throw new NotSupportedException(); + }; + } + + private void AddTokenToMutableModule(TypeSystemEntity entity) + { + var existingToken = entity switch + { + TypeDesc typeDesc => _tokenResolver.GetModuleTokenForType(typeDesc, allowDynamicallyCreatedReference: true, throwIfNotFound: false), + MethodDesc methodDesc => _tokenResolver.GetModuleTokenForMethod(methodDesc, allowDynamicallyCreatedReference: true, throwIfNotFound: false), + FieldDesc fieldDesc => _tokenResolver.GetModuleTokenForField(fieldDesc, allowDynamicallyCreatedReference: true, throwIfNotFound: false), + _ => throw new NotSupportedException() + }; + + if (!existingToken.IsNull) + return; + + if (!_mutableModule.TryGetEntityHandle(entity).HasValue) + { + throw new InternalCompilerErrorException($"Unable to create token to {entity}"); + } + } + + private void EnsureMethodDefTokensAreAvailableInVersionBubble(MethodDesc methodDesc) + { + if (!methodDesc.IsPrimaryMethodDesc()) + { + EnsureMethodDefTokensAreAvailableInVersionBubble(methodDesc.GetPrimaryMethodDesc()); + return; + } + if (methodDesc is EcmaMethod ecmaMethod) + { + AddTokenToMutableModule(ecmaMethod); + return; + } + if (methodDesc.HasInstantiation) + { + EnsureTypeDefTokensAreAvailableInVersionBubble(methodDesc.GetMethodDefinition().OwningType); + foreach (TypeDesc instParam in methodDesc.Instantiation) + { + EnsureTypeDefTokensAreAvailableInVersionBubble(instParam); + } + EnsureMethodDefTokensAreAvailableInVersionBubble(methodDesc.GetTypicalMethodDefinition()); + } + } + + private void EnsureFieldDefTokensAreAvailableInVersionBubble(FieldDesc fieldDesc) + { + EnsureTypeDefTokensAreAvailableInVersionBubble(fieldDesc.OwningType); + EnsureTypeDefTokensAreAvailableInVersionBubble(fieldDesc.FieldType); + if (fieldDesc is EcmaField ecmaField) + { + AddTokenToMutableModule(ecmaField); + } + else + { + EnsureFieldDefTokensAreAvailableInVersionBubble(fieldDesc.GetTypicalFieldDefinition()); + } + } + + private void EnsureTypeDefTokensAreAvailableInVersionBubble(TypeDesc type) + { + // Type represented by simple element type + if (type.IsPrimitive || type.IsVoid || type.IsObject || type.IsString || type.IsTypedReference) + return; + + if (type is EcmaType ecmaType) + { + AddTokenToMutableModule(ecmaType); + return; + } + if (type is ParameterizedType parameterizedType) + { + EnsureTypeDefTokensAreAvailableInVersionBubble(parameterizedType.ParameterType); + AddTokenToMutableModule(parameterizedType); + return; + } + + if (type.HasInstantiation) + { + EnsureTypeDefTokensAreAvailableInVersionBubble(type.GetTypeDefinition()); + + foreach (TypeDesc instParam in type.Instantiation) + { + EnsureTypeDefTokensAreAvailableInVersionBubble(instParam); + } + } + else if (type.IsParameterizedType) + { + EnsureTypeDefTokensAreAvailableInVersionBubble(type.GetParameterType()); + } + } + } +} diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunCodegenCompilation.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunCodegenCompilation.cs index c1b858a839db05..93ef4231f0a678 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunCodegenCompilation.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunCodegenCompilation.cs @@ -19,6 +19,7 @@ using ILCompiler.DependencyAnalysis; using ILCompiler.DependencyAnalysis.ReadyToRun; using ILCompiler.DependencyAnalysisFramework; +using ILCompiler.ReadyToRun; using ILCompiler.Reflection.ReadyToRun; using Internal.TypeSystem.Ecma; using ILCompiler.ReadyToRun.TypeSystem; @@ -315,6 +316,8 @@ public sealed class ReadyToRunCodegenCompilation : Compilation private ConcurrentDictionary _computedFixedLayoutTypes = new ConcurrentDictionary(); private Func _computedFixedLayoutTypesUncached; + private readonly ExternalReferenceTokenManager _tokenManager; + internal ReadyToRunCodegenCompilation( DependencyAnalyzerBase dependencyGraph, NodeFactory nodeFactory, @@ -387,6 +390,7 @@ internal ReadyToRunCodegenCompilation( _profileData = profileData; _fileLayoutOptimizer = new FileLayoutOptimizer(logger, methodLayoutAlgorithm, fileLayoutAlgorithm, profileData, _nodeFactory); + _tokenManager = new ExternalReferenceTokenManager(_nodeFactory.ManifestMetadataTable._mutableModule, _nodeFactory.Resolver); } private readonly static string s_folderUpPrefix = ".." + Path.DirectorySeparatorChar; @@ -729,11 +733,12 @@ protected override void ComputeDependencyNodeDependencies(List + { + switch(il.GetObject(tok)) + { + case TypeSystemEntity tse: + _tokenManager.EnsureDefTokensAreAvailable(tse, ((EcmaMethod)method.GetPrimaryMethodDesc().GetTypicalMethodDefinition()).Module, true); + break; + default: + // We don't need to worry about string handles + break; + } + return tok; + }); + // ILTokenReplacer doesn't handle exception regions or local variable types, so handle those separately + var exceptionRegions = (ILExceptionRegion[])il.GetExceptionRegions(); + for (int i = 0; i < exceptionRegions.Length; i++) + { + var region = exceptionRegions[i]; + if (region.Kind == ILExceptionRegionKind.Catch) + { + TypeSystemEntity catchType = (TypeSystemEntity)il.GetObject(region.ClassToken); + _tokenManager.EnsureDefTokensAreAvailable(catchType, ((EcmaMethod)method.GetPrimaryMethodDesc().GetTypicalMethodDefinition()).Module, true); + } + } + foreach (var local in il.GetLocals()) + { + _tokenManager.EnsureDefTokensAreAvailable(local.Type, ((EcmaMethod)method.GetPrimaryMethodDesc().GetTypicalMethodDefinition()).Module, true); + } + } + void ProcessMutableMethodBodiesList() { MethodDesc[] mutableMethodBodyNeedList = new MethodDesc[_methodsWhichNeedMutableILBodies.Count]; @@ -954,51 +1002,11 @@ private void EnsureInstantiationReferencesArePresentForExternalMethod(MethodDesc { // Validate that the typedef tokens for all of the instantiation parameters of the method // have tokens. + var moduleForNewReferences = ((EcmaMethod)method.GetPrimaryMethodDesc().GetTypicalMethodDefinition()).Module; foreach (var type in method.Instantiation) - EnsureTypeDefTokensAreReady(type); + _tokenManager.EnsureDefTokensAreAvailable(type, moduleForNewReferences, false); foreach (var type in method.OwningType.Instantiation) - EnsureTypeDefTokensAreReady(type); - - void EnsureTypeDefTokensAreReady(TypeDesc type) - { - // Type represented by simple element type - if (type.IsPrimitive || type.IsVoid || type.IsObject || type.IsString || type.IsTypedReference) - return; - - if (type is EcmaType ecmaType) - { - if (!_nodeFactory.Resolver.GetModuleTokenForType(ecmaType, allowDynamicallyCreatedReference: false, throwIfNotFound: false).IsNull) - return; - try - { - Debug.Assert(_nodeFactory.CompilationModuleGroup.CrossModuleInlineableModule(ecmaType.Module)); - _nodeFactory.ManifestMetadataTable._mutableModule.ModuleThatIsCurrentlyTheSourceOfNewReferences - = ecmaType.Module; - if (!_nodeFactory.ManifestMetadataTable._mutableModule.TryGetEntityHandle(ecmaType).HasValue) - throw new InternalCompilerErrorException($"Unable to create token to {ecmaType}"); - } - finally - { - _nodeFactory.ManifestMetadataTable._mutableModule.ModuleThatIsCurrentlyTheSourceOfNewReferences - = null; - } - return; - } - - if (type.HasInstantiation) - { - EnsureTypeDefTokensAreReady(type.GetTypeDefinition()); - - foreach (TypeDesc instParam in type.Instantiation) - { - EnsureTypeDefTokensAreReady(instParam); - } - } - else if (type.IsParameterizedType) - { - EnsureTypeDefTokensAreReady(type.GetParameterType()); - } - } + _tokenManager.EnsureDefTokensAreAvailable(type, moduleForNewReferences, false); } private void AddNecessaryAsyncReferences(MethodDesc method) @@ -1035,32 +1043,9 @@ private void AddNecessaryAsyncReferences(MethodDesc method) asyncHelpers.GetKnownMethod("AllocContinuationClass"u8, null), asyncHelpers.GetKnownMethod("AllocContinuationMethod"u8, null), ]; - _nodeFactory.ManifestMetadataTable._mutableModule.ModuleThatIsCurrentlyTheSourceOfNewReferences = ((EcmaMethod)method.GetPrimaryMethodDesc().GetTypicalMethodDefinition()).Module; - try - { - // Unconditionally add references to the MutableModule. These members are internal / private and - // shouldn't be referenced already, and this lets us avoid doing this more than once - foreach (var td in requiredTypes) - { - if (!_nodeFactory.ManifestMetadataTable._mutableModule.TryGetEntityHandle(td).HasValue) - throw new InternalCompilerErrorException($"Unable to create token to {td}"); - } - foreach (var fd in requiredFields) - { - if (!_nodeFactory.ManifestMetadataTable._mutableModule.TryGetEntityHandle(fd).HasValue) - throw new InternalCompilerErrorException($"Unable to create token to {fd}"); - } - foreach (var md in requiredMethods) - { - if (!_nodeFactory.ManifestMetadataTable._mutableModule.TryGetEntityHandle(md).HasValue) - throw new InternalCompilerErrorException($"Unable to create token to {md}"); - } - _hasAddedAsyncReferences = true; - } - finally - { - _nodeFactory.ManifestMetadataTable._mutableModule.ModuleThatIsCurrentlyTheSourceOfNewReferences = null; - } + var moduleForNewReferences = ((EcmaMethod)method.GetPrimaryMethodDesc().GetTypicalMethodDefinition()).Module; + _tokenManager.EnsureDefTokensAreAvailable([..requiredMethods, ..requiredTypes, ..requiredFields], moduleForNewReferences, true); + _hasAddedAsyncReferences = true; } public ISymbolNode GetFieldRvaData(FieldDesc field) diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/IL/ReadyToRunILProvider.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/IL/ReadyToRunILProvider.cs index c1f74a8a550223..961c634100caa5 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/IL/ReadyToRunILProvider.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/IL/ReadyToRunILProvider.cs @@ -15,6 +15,7 @@ using System.Reflection.Metadata; using System.Reflection.Metadata.Ecma335; using ILCompiler.ReadyToRun.TypeSystem; +using ILCompiler.DependencyAnalysis.ReadyToRun; namespace Internal.IL { @@ -149,71 +150,15 @@ public void CreateCrossModuleInlineableTokensForILBody(MethodDesc method) Debug.Assert(_manifestMutableModule != null); var wrappedMethodIL = new ManifestModuleWrappedMethodIL(); - // 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()) - { - var amv = (AsyncMethodVariant)method; - if (NeedsAsyncThunk(method)) - { - 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.IsAsync) - { - 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 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 - // the compilation, which is version safe. - wrappedMethodIL = null; - } - } - else - { - Debug.Assert(!_compilationModuleGroup.VersionsWithMethodBody(method) && - _compilationModuleGroup.CrossModuleInlineable(method)); + Debug.Assert(!_compilationModuleGroup.VersionsWithMethodBody(method) && + _compilationModuleGroup.CrossModuleInlineable(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 - // the compilation, which is version safe. - wrappedMethodIL = null; - } + if (!wrappedMethodIL.Initialize(_manifestMutableModule, EcmaMethodIL.Create((EcmaMethod)method.GetPrimaryMethodDesc().GetTypicalMethodDefinition()))) + { + // 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; } _manifestModuleWrappedMethods.Add(method, wrappedMethodIL); @@ -224,13 +169,7 @@ public bool NeedsCrossModuleInlineableTokens(MethodDesc method) { bool regularCrossModuleInlineable = (!_compilationModuleGroup.VersionsWithMethodBody(method) && _compilationModuleGroup.CrossModuleInlineable(method)); - bool requiredCrossModuleInliningForAsync = (NeedsTaskReturningThunk(method) || NeedsAsyncThunk(method)) - && !_compilationModuleGroup.VersionsWithModule(method.Context.SystemModule); - // AsyncResumptionStub always uses synthetic faux IL with tokens that reference - // ParameterizedType/InstantiatedType. These tokens must be wrapped in manifest - // module tokens regardless of whether CoreLib is in the version bubble. - bool resumptionStubNeedsTokens = method is AsyncResumptionStub; - if ((regularCrossModuleInlineable || requiredCrossModuleInliningForAsync || resumptionStubNeedsTokens) + if ((regularCrossModuleInlineable) && !_manifestModuleWrappedMethods.ContainsKey(method)) { return true; @@ -238,7 +177,7 @@ public bool NeedsCrossModuleInlineableTokens(MethodDesc method) return false; } - bool NeedsTaskReturningThunk(MethodDesc method) + private static bool NeedsTaskReturningThunk(MethodDesc method) { Debug.Assert(method.IsTypicalMethodDefinition); if (method is not EcmaMethod ecmaMethod) @@ -256,7 +195,7 @@ bool NeedsTaskReturningThunk(MethodDesc method) return false; } - bool NeedsAsyncThunk(MethodDesc method) + private static bool NeedsAsyncThunk(MethodDesc method) { Debug.Assert(method.IsTypicalMethodDefinition); if (method is not AsyncMethodVariant) @@ -264,7 +203,7 @@ bool NeedsAsyncThunk(MethodDesc method) return !method.IsAsync; } - MethodIL GetMethodILForAsyncMethod(MethodDesc method) + private MethodIL GetMethodILForAsyncMethod(MethodDesc method) { Debug.Assert(method.IsAsync && method is EcmaMethod); if (method.Signature.ReturnsTaskOrValueTask()) @@ -327,8 +266,6 @@ public override MethodIL GetMethodIL(MethodDesc method) } else if (method is AsyncResumptionStub ars) { - if (_manifestModuleWrappedMethods.TryGetValue(ars, out var methodil)) - return methodil; return ars.EmitIL(); } else @@ -349,7 +286,6 @@ class ManifestModuleWrappedMethodIL : MethodIL, IEcmaMethodIL, IMethodTokensAreU ILExceptionRegion[] _exceptionRegions; byte[] _ilBytes; LocalVariableDefinition[] _locals; - HashSet _methodsWithAsyncVariants; MutableModule _mutableModule; @@ -357,20 +293,10 @@ public ManifestModuleWrappedMethodIL() {} public bool Initialize(MutableModule mutableModule, EcmaMethodIL wrappedMethod) { - return Initialize(mutableModule, wrappedMethod, wrappedMethod.OwningMethod, true); - } - - public bool Initialize(MutableModule mutableModule, MethodIL wrappedMethod, MethodDesc owningMethod, bool validateStandaloneMetadata) - { - HashSet methodsWhichCannotHaveAsyncVariants = null; - _methodsWithAsyncVariants = null; - - if (wrappedMethod == null) - return false; - bool failedToReplaceToken = false; try { + var owningMethod = wrappedMethod.OwningMethod; Debug.Assert(mutableModule.ModuleThatIsCurrentlyTheSourceOfNewReferences == null); mutableModule.ModuleThatIsCurrentlyTheSourceOfNewReferences = ((EcmaMethod)owningMethod.GetPrimaryMethodDesc().GetTypicalMethodDefinition()).Module; var owningMethodHandle = mutableModule.TryGetEntityHandle(owningMethod); @@ -400,8 +326,7 @@ public bool Initialize(MutableModule mutableModule, MethodIL wrappedMethod, Meth ILTokenReplacer.Replace(_ilBytes, GetMutableModuleToken); #if DEBUG - if (validateStandaloneMetadata) - Debug.Assert(ReadyToRunStandaloneMethodMetadata.Compute((EcmaMethod)_owningMethod.GetPrimaryMethodDesc().GetTypicalMethodDefinition()) != null); + Debug.Assert(ReadyToRunStandaloneMethodMetadata.Compute((EcmaMethod)_owningMethod.GetPrimaryMethodDesc().GetTypicalMethodDefinition()) != null); #endif // DEBUG } finally @@ -422,41 +347,6 @@ int GetMutableModuleToken(int token) } else { - // Since async thunks directly refer to async methods(which is otherwise not permitted in IL), we need to track this detail - // when we replace the tokens, and use tokens for the non-async variant method, but return - // the async variant as appropriate. - if (result is MethodDesc methodDesc) - { - if (methodDesc.IsAsyncVariant()) - { - // We actually need to store the non-variant method, and force GetObject - // to return the async variant - methodDesc = methodDesc.GetTargetOfAsyncVariant(); - if (_methodsWithAsyncVariants == null) - _methodsWithAsyncVariants = new HashSet(); - _methodsWithAsyncVariants.Add(methodDesc); - result = methodDesc; - - if (methodsWhichCannotHaveAsyncVariants != null && - methodsWhichCannotHaveAsyncVariants.Contains(methodDesc)) - { - // This method cannot refer to both an async thunk and async variant, fail the compile - throw new Exception("Method refers in IL directly to an async variant method and a non-async variant"); - } - } - else if (methodDesc.IsAsync) - { - if (methodsWhichCannotHaveAsyncVariants == null) - methodsWhichCannotHaveAsyncVariants = new HashSet(); - methodsWhichCannotHaveAsyncVariants.Add(methodDesc); - if (_methodsWithAsyncVariants != null && - _methodsWithAsyncVariants.Contains(methodDesc)) - { - // This method cannot refer to both an async thunk and async variant, fail the compile - throw new Exception("Method refers in IL directly to an async variant method and a non-async variant"); - } - } - } newToken = mutableModule.TryGetHandle((TypeSystemEntity)result); } if (!newToken.HasValue) @@ -486,14 +376,7 @@ public override object GetObject(int token, NotFoundBehavior notFoundBehavior = if ((token & 0xFF000000) == 0x70000000) return _mutableModule.GetUserString(System.Reflection.Metadata.Ecma335.MetadataTokens.UserStringHandle(token)); - object result = _mutableModule.GetObject(System.Reflection.Metadata.Ecma335.MetadataTokens.EntityHandle(token), notFoundBehavior); - if (_methodsWithAsyncVariants != null && - _methodsWithAsyncVariants.Contains(result)) - { - // Return the async variant method - result = ((MethodDesc)result).GetAsyncVariant(); - } - return result; + return _mutableModule.GetObject(System.Reflection.Metadata.Ecma335.MetadataTokens.EntityHandle(token), notFoundBehavior); } } diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ILCompiler.ReadyToRun.csproj b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ILCompiler.ReadyToRun.csproj index 7c69dee450ee6d..441b6b533fa2fe 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ILCompiler.ReadyToRun.csproj +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ILCompiler.ReadyToRun.csproj @@ -306,6 +306,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 0be908211636f7..92738de737625d 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/JitInterface/CorInfoImpl.ReadyToRun.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/JitInterface/CorInfoImpl.ReadyToRun.cs @@ -55,11 +55,13 @@ public class FieldWithToken : IEquatable public readonly FieldDesc Field; public readonly ModuleToken Token; public readonly bool OwningTypeNotDerivedFromToken; + private readonly bool _forceOwningTypeNotDerivedFromToken; - public FieldWithToken(FieldDesc field, ModuleToken token) + public FieldWithToken(FieldDesc field, ModuleToken token, bool forceOwningTypeNotDerivedFromToken = false) { Field = field; Token = token; + _forceOwningTypeNotDerivedFromToken = forceOwningTypeNotDerivedFromToken; if (token.TokenType == CorTokenType.mdtMemberRef) { var memberRef = token.MetadataReader.GetMemberReference((MemberReferenceHandle)token.Handle); @@ -75,6 +77,10 @@ public FieldWithToken(FieldDesc field, ModuleToken token) break; } } + if (_forceOwningTypeNotDerivedFromToken) + { + OwningTypeNotDerivedFromToken = true; + } } public override bool Equals(object obj) @@ -93,7 +99,9 @@ public bool Equals(FieldWithToken fieldWithToken) if (fieldWithToken == null) return false; - return Field == fieldWithToken.Field && Token.Equals(fieldWithToken.Token); + return Field == fieldWithToken.Field + && Token.Equals(fieldWithToken.Token) + && _forceOwningTypeNotDerivedFromToken == fieldWithToken._forceOwningTypeNotDerivedFromToken; } public void AppendMangledName(NameMangler nameMangler, Utf8StringBuilder sb) @@ -121,8 +129,10 @@ public int CompareTo(FieldWithToken other, TypeSystemComparer comparer) result = comparer.Compare(Field, other.Field); if (result != 0) return result; - - return Token.CompareTo(other.Token); + result = Token.CompareTo(other.Token); + if (result != 0) + return result; + return _forceOwningTypeNotDerivedFromToken.CompareTo(other._forceOwningTypeNotDerivedFromToken); } } @@ -133,19 +143,30 @@ public class MethodWithToken public readonly TypeDesc ConstrainedType; public readonly bool Unboxing; public readonly bool OwningTypeNotDerivedFromToken; + private readonly bool _forceOwningTypeNotDerivedFromToken; public readonly TypeDesc OwningType; - public MethodWithToken(MethodDesc method, ModuleToken token, TypeDesc constrainedType, bool unboxing, TypeSystemEntity context, TypeDesc devirtualizedMethodOwner = null) + public MethodWithToken(MethodDesc method, ModuleToken token, TypeDesc constrainedType, bool unboxing, TypeSystemEntity genericContextObject, TypeDesc devirtualizedMethodOwner = null, bool forceOwningTypeFromMethodDesc = false) { Debug.Assert(!method.IsUnboxingThunk()); + Debug.Assert(genericContextObject is null or MethodDesc or TypeDesc); Method = method; Token = token; ConstrainedType = constrainedType; Unboxing = unboxing; - OwningType = GetMethodTokenOwningType(this, constrainedType, context, devirtualizedMethodOwner, out OwningTypeNotDerivedFromToken); + _forceOwningTypeNotDerivedFromToken = forceOwningTypeFromMethodDesc; + if (!_forceOwningTypeNotDerivedFromToken) + { + OwningType = GetMethodTokenOwningType(this, constrainedType, genericContextObject, devirtualizedMethodOwner, out OwningTypeNotDerivedFromToken); + } + else + { + OwningType = method.OwningType; + OwningTypeNotDerivedFromToken = true; + } } - private static TypeDesc GetMethodTokenOwningType(MethodWithToken methodToken, TypeDesc constrainedType, TypeSystemEntity context, TypeDesc devirtualizedMethodOwner, out bool owningTypeNotDerivedFromToken) + private static TypeDesc GetMethodTokenOwningType(MethodWithToken methodToken, TypeDesc constrainedType, TypeSystemEntity genericContextObject, TypeDesc devirtualizedMethodOwner, out bool owningTypeNotDerivedFromToken) { ModuleToken moduleToken = methodToken.Token; owningTypeNotDerivedFromToken = false; @@ -160,7 +181,7 @@ private static TypeDesc GetMethodTokenOwningType(MethodWithToken methodToken, Ty if (moduleToken.TokenType == CorTokenType.mdtMethodDef) { var methodDefinition = moduleToken.MetadataReader.GetMethodDefinition((MethodDefinitionHandle)moduleToken.Handle); - return HandleContext(moduleToken.Module, methodDefinition.GetDeclaringType(), methodToken.Method.OwningType, constrainedType, context, devirtualizedMethodOwner, ref owningTypeNotDerivedFromToken); + return HandleContext(moduleToken.Module, methodDefinition.GetDeclaringType(), methodToken.Method.OwningType, constrainedType, genericContextObject, devirtualizedMethodOwner, ref owningTypeNotDerivedFromToken); } // At this point moduleToken must point at a MemberRef. @@ -173,19 +194,19 @@ private static TypeDesc GetMethodTokenOwningType(MethodWithToken methodToken, Ty case HandleKind.TypeSpecification: { Debug.Assert(devirtualizedMethodOwner == null); // Devirtualization is expected to always use a methoddef token - return HandleContext(moduleToken.Module, memberRef.Parent, methodToken.Method.OwningType, constrainedType, context, null, ref owningTypeNotDerivedFromToken); + return HandleContext(moduleToken.Module, memberRef.Parent, methodToken.Method.OwningType, constrainedType, genericContextObject, null, ref owningTypeNotDerivedFromToken); } default: return methodToken.Method.OwningType; } - TypeDesc HandleContext(IEcmaModule module, EntityHandle handle, TypeDesc methodTargetOwner, TypeDesc constrainedType, TypeSystemEntity context, TypeDesc devirtualizedMethodOwner, ref bool owningTypeNotDerivedFromToken) + static TypeDesc HandleContext(IEcmaModule module, EntityHandle handle, TypeDesc methodTargetOwner, TypeDesc constrainedType, TypeSystemEntity genericContextObject, TypeDesc devirtualizedMethodOwner, ref bool owningTypeNotDerivedFromToken) { var tokenOnlyOwningType = module.GetType(handle); TypeDesc actualOwningType; - if (context == null) + if (genericContextObject == null) { actualOwningType = methodTargetOwner; } @@ -194,14 +215,14 @@ TypeDesc HandleContext(IEcmaModule module, EntityHandle handle, TypeDesc methodT Instantiation typeInstantiation; Instantiation methodInstantiation = new Instantiation(); - if (context is MethodDesc methodContext) + if (genericContextObject is MethodDesc methodContext) { typeInstantiation = methodContext.OwningType.Instantiation; methodInstantiation = methodContext.Instantiation; } else { - TypeDesc typeContext = (TypeDesc)context; + TypeDesc typeContext = (TypeDesc)genericContextObject; typeInstantiation = typeContext.Instantiation; } @@ -321,14 +342,12 @@ public bool Equals(MethodWithToken methodWithToken) if (methodWithToken == null) return false; - bool equals = Method == methodWithToken.Method && Token.Equals(methodWithToken.Token) - && OwningType == methodWithToken.OwningType && ConstrainedType == methodWithToken.ConstrainedType - && Unboxing == methodWithToken.Unboxing; - if (equals) - { - Debug.Assert(OwningTypeNotDerivedFromToken == methodWithToken.OwningTypeNotDerivedFromToken); - Debug.Assert(OwningType == methodWithToken.OwningType); - } + bool equals = Method == methodWithToken.Method + && Token.Equals(methodWithToken.Token) + && OwningType == methodWithToken.OwningType + && ConstrainedType == methodWithToken.ConstrainedType + && Unboxing == methodWithToken.Unboxing + && _forceOwningTypeNotDerivedFromToken == methodWithToken._forceOwningTypeNotDerivedFromToken; return equals; } @@ -404,17 +423,9 @@ public int CompareTo(MethodWithToken other, TypeSystemComparer comparer) if (result != 0) return result; - // The OwningType/OwningTypeNotDerivedFromToken should be equivalent if the above conditions are equal. - Debug.Assert(OwningTypeNotDerivedFromToken == other.OwningTypeNotDerivedFromToken); - Debug.Assert(OwningTypeNotDerivedFromToken || (OwningType == other.OwningType)); - - if (OwningTypeNotDerivedFromToken != other.OwningTypeNotDerivedFromToken) - { - if (OwningTypeNotDerivedFromToken) - return 1; - else - return -1; - } + result = _forceOwningTypeNotDerivedFromToken.CompareTo(other._forceOwningTypeNotDerivedFromToken); + if (result != 0) + return result; return comparer.Compare(OwningType, other.OwningType); } @@ -564,15 +575,7 @@ public static bool ShouldSkipCompilation(InstructionSetSupport instructionSetSup // Special methods on delegate types return true; } - // Async resumption stubs use faux IL with synthetic tokens. When CoreLib is in the - // version bubble the stubs are not wrapped with ManifestModuleWrappedMethodIL, so - // token resolution for InstantiatedType / ParameterizedType falls through to a path - // that cannot handle them. Skip compilation and let the runtime JIT these stubs. - // https://github.com/dotnet/runtime/issues/125337 - if (methodNeedingCode.IsCompilerGeneratedILBodyForAsync() && compilation != null && compilation.NodeFactory.CompilationModuleGroup.IsCompositeBuildMode) - { - return true; - } + if (ShouldCodeNotBeCompiledIntoFinalImage(instructionSetSupport, methodNeedingCode)) { return true; @@ -971,10 +974,11 @@ private void getReadyToRunDelegateCtorHelper(ref CORINFO_RESOLVED_TOKEN pTargetM MethodWithToken targetMethod = new MethodWithToken( targetMethodDesc, - HandleToModuleToken(ref pTargetMethod), + HandleToModuleToken(ref pTargetMethod, out bool strippedInstantiation), constrainedType: constrainedType, unboxing: false, - context: typeOrMethodContext); + genericContextObject: typeOrMethodContext, + forceOwningTypeFromMethodDesc: strippedInstantiation); // runtime lookup is not needed, callerHandle is unused pLookup.lookupKind.needsRuntimeLookup = false; @@ -1382,13 +1386,13 @@ private bool canTailCall(CORINFO_METHOD_STRUCT_* callerHnd, CORINFO_METHOD_STRUC private FieldWithToken ComputeFieldWithToken(FieldDesc field, ref CORINFO_RESOLVED_TOKEN pResolvedToken) { - ModuleToken token = HandleToModuleToken(ref pResolvedToken); - return new FieldWithToken(field, token); + ModuleToken token = HandleToModuleToken(ref pResolvedToken, out bool strippedInstantiation); + return new FieldWithToken(field, token, strippedInstantiation); } private MethodWithToken ComputeMethodWithToken(MethodDesc method, ref CORINFO_RESOLVED_TOKEN pResolvedToken, TypeDesc constrainedType, bool unboxing) { - ModuleToken token = HandleToModuleToken(ref pResolvedToken, method, out TypeSystemEntity context, ref constrainedType); + ModuleToken token = _HandleToModuleToken(ref pResolvedToken, method, out TypeSystemEntity context, ref constrainedType, out bool strippedInstantiation); TypeDesc devirtualizedMethodOwner = null; if (pResolvedToken.tokenType == CorInfoTokenKind.CORINFO_TOKENKIND_DevirtualizedMethod) @@ -1396,42 +1400,46 @@ private MethodWithToken ComputeMethodWithToken(MethodDesc method, ref CORINFO_RE devirtualizedMethodOwner = HandleToObject(pResolvedToken.hClass); } - return new MethodWithToken(method, token, constrainedType: constrainedType, unboxing: unboxing, context: context, devirtualizedMethodOwner: devirtualizedMethodOwner); - } + // For crossgen-generated IL stubs, we may only have access to the method token for the typical method definition. + // This means that the owning type of the method may not be encoded in the method token. + // bool methodTokenMightNotEncodeOwningType = MethodBeingCompiled.IsCompilerGeneratedILBodyForAsync(); + return new MethodWithToken(method, token, constrainedType: constrainedType, unboxing: unboxing, genericContextObject: context, devirtualizedMethodOwner: devirtualizedMethodOwner, forceOwningTypeFromMethodDesc: strippedInstantiation); - 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) - || methodDesc.IsPInvoke)) + ModuleToken _HandleToModuleToken(ref CORINFO_RESOLVED_TOKEN pResolvedToken, MethodDesc methodDesc, out TypeSystemEntity context, ref TypeDesc constrainedType, out bool strippedInstantiation) { - if ((CorTokenType)(unchecked((uint)pResolvedToken.token) & 0xFF000000u) == CorTokenType.mdtMethodDef && - methodDesc?.GetTypicalMethodDefinition() is EcmaMethod ecmaMethod) + if (methodDesc != null && (_compilation.NodeFactory.CompilationModuleGroup.VersionsWithMethodBody(methodDesc) + || (pResolvedToken.tokenType == CorInfoTokenKind.CORINFO_TOKENKIND_DevirtualizedMethod) + || methodDesc.IsPInvoke)) { - mdToken token = (mdToken)MetadataTokens.GetToken(ecmaMethod.Handle); + if ((CorTokenType)(unchecked((uint)pResolvedToken.token) & 0xFF000000u) == CorTokenType.mdtMethodDef && + methodDesc?.GetTypicalMethodDefinition() is EcmaMethod ecmaMethod) + { + mdToken token = (mdToken)MetadataTokens.GetToken(ecmaMethod.Handle); - // This is used for de-virtualization of non-generic virtual methods, and should be treated - // as a the methodDesc parameter defining the exact OwningType, not doing resolution through the token. - context = null; - constrainedType = null; + // This is used for de-virtualization of non-generic virtual methods, and should be treated + // as a the methodDesc parameter defining the exact OwningType, not doing resolution through the token. + context = null; + constrainedType = null; + strippedInstantiation = false; - return new ModuleToken(ecmaMethod.Module, token); + return new ModuleToken(ecmaMethod.Module, token); + } } - } - if (pResolvedToken.tokenType == CorInfoTokenKind.CORINFO_TOKENKIND_ResolvedStaticVirtualMethod) - { - context = null; - } - else - { - context = entityFromContext(pResolvedToken.tokenContext); - } + if (pResolvedToken.tokenType == CorInfoTokenKind.CORINFO_TOKENKIND_ResolvedStaticVirtualMethod) + { + context = null; + } + else + { + context = entityFromContext(pResolvedToken.tokenContext); + } - return HandleToModuleToken(ref pResolvedToken); + return HandleToModuleToken(ref pResolvedToken, out strippedInstantiation); + } } - private ModuleToken HandleToModuleToken(ref CORINFO_RESOLVED_TOKEN pResolvedToken) + private ModuleToken HandleToModuleToken(ref CORINFO_RESOLVED_TOKEN pResolvedToken, out bool strippedInstantiation) { mdToken token = pResolvedToken.token; var methodIL = HandleToObject(pResolvedToken.tokenScope); @@ -1458,7 +1466,11 @@ private ModuleToken HandleToModuleToken(ref CORINFO_RESOLVED_TOKEN pResolvedToke // It's okay to strip the instantiation away because we don't need a MethodSpec // token - SignatureBuilder will generate the generic method signature // using instantiation parameters from the MethodDesc entity. - resultMethod = resultMethod.GetPrimaryMethodDesc().GetTypicalMethodDefinition(); + // However, we need to make note of this to indicate that the OwningType of the + // method should be inferred from the MethodDesc entity rather than the token. + var primaryMethod = resultMethod.GetPrimaryMethodDesc(); + resultMethod = primaryMethod.GetTypicalMethodDefinition(); + strippedInstantiation = resultMethod != primaryMethod; if (!_compilation.NodeFactory.CompilationModuleGroup.VersionsWithType(resultMethod.OwningType)) { @@ -1475,19 +1487,27 @@ private ModuleToken HandleToModuleToken(ref CORINFO_RESOLVED_TOKEN pResolvedToke // instantiated MemberRef token - SignatureBuilder will generate the generic // field signature using instantiation parameters from the FieldDesc entity. resultField = resultField.GetTypicalFieldDefinition(); + strippedInstantiation = resultField != resultDef; + if(!(_compilation.NodeFactory.CompilationModuleGroup.VersionsWithType(resultField.OwningType) || resultField.OwningType.IsNonVersionable())) + { + ModuleToken result = _compilation.NodeFactory.Resolver.GetModuleTokenForField(resultField, allowDynamicallyCreatedReference: true, throwIfNotFound: true); + return result; + } Debug.Assert(resultField is EcmaField); - Debug.Assert(_compilation.NodeFactory.CompilationModuleGroup.VersionsWithType(resultField.OwningType) || resultField.OwningType.IsNonVersionable()); token = (mdToken)MetadataTokens.GetToken(((EcmaField)resultField).Handle); module = ((EcmaField)resultField).Module; } else { + // We don't strip the instantiation for instantiated types in GetModuleTokenForType + strippedInstantiation = false; return GetModuleTokenForType((TypeSystemEntity)resultDef); } } else { + strippedInstantiation = false; module = ((IEcmaMethodIL)methodILDef).Module; } @@ -1524,8 +1544,16 @@ private InfoAccessType constructStringLiteral(CORINFO_MODULE_STRUCT_* module, md { MethodILScope methodIL = HandleToObject(module); - // If this is not a MethodIL backed by a physical method body, we need to remap the token. - Debug.Assert(methodIL.GetMethodILScopeDefinition() is IEcmaMethodIL); + Debug.Assert(methodIL.GetMethodILScopeDefinition() is IEcmaMethodIL + // We may be calling this from emptyStringLiteral for a method inlined into a thunk + || metaTok == (mdToken)CorTokenType.mdtString); + if (metaTok == (mdToken)CorTokenType.mdtString) + { + ISymbolNode str = _compilation.SymbolNodeFactory.StringLiteral( + new ModuleToken(_compilation.NodeFactory.ManifestMetadataTable._mutableModule, metaTok)); + ppValue = (void*)ObjectToHandle(str); + return InfoAccessType.IAT_PPVALUE; + } IEcmaModule metadataModule = ((IEcmaMethodIL)methodIL.GetMethodILScopeDefinition()).Module; ISymbolNode stringObject = _compilation.SymbolNodeFactory.StringLiteral( @@ -2741,12 +2769,16 @@ private void ComputeRuntimeLookupForSharedGenericToken( { var methodIL = HandleToObject(pResolvedToken.tokenScope); MethodDesc sharedMethod = methodIL.OwningMethod.GetSharedRuntimeFormMethodTarget(); + // We shouldn't be needing shared generics in resumption stubs - generics info should all be stored in the continuation + Debug.Assert(MethodBeingCompiled is not AsyncResumptionStub); _compilation.NodeFactory.DetectGenericCycles(MethodBeingCompiled, sharedMethod); - helperArg = new MethodWithToken(methodDesc, HandleToModuleToken(ref pResolvedToken), constrainedType, unboxing: false, context: sharedMethod); + helperArg = new MethodWithToken(methodDesc, HandleToModuleToken(ref pResolvedToken, out bool strippedInstantiation), constrainedType, unboxing: false, genericContextObject: sharedMethod, forceOwningTypeFromMethodDesc: strippedInstantiation); } else if (helperArg is FieldDesc fieldDesc) { - helperArg = new FieldWithToken(fieldDesc, HandleToModuleToken(ref pResolvedToken)); + ModuleToken fieldToken = HandleToModuleToken(ref pResolvedToken, out bool strippedInstantiation); + Debug.Assert(!strippedInstantiation); + helperArg = new FieldWithToken(fieldDesc, fieldToken, forceOwningTypeNotDerivedFromToken: strippedInstantiation); } var methodContext = new GenericContext(callerHandle); @@ -3169,7 +3201,7 @@ private void getAddressOfPInvokeTarget(CORINFO_METHOD_STRUCT_* method, ref CORIN Debug.Assert(_compilation.CompilationModuleGroup.VersionsWithMethodBody(methodDesc)); EcmaMethod ecmaMethod = (EcmaMethod)methodDesc; ModuleToken moduleToken = new ModuleToken(ecmaMethod.Module, ecmaMethod.Handle); - MethodWithToken methodWithToken = new MethodWithToken(ecmaMethod, moduleToken, constrainedType: null, unboxing: false, context: null); + MethodWithToken methodWithToken = new MethodWithToken(ecmaMethod, moduleToken, constrainedType: null, unboxing: false, genericContextObject: null); if ((ecmaMethod.GetPInvokeMethodCallingConventions() & UnmanagedCallingConventions.IsSuppressGcTransition) != 0) { diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/TypeSystem/MethodDescExtensions.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/TypeSystem/MethodDescExtensions.cs index 935a160ee4f6ea..8694092b5fd930 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/TypeSystem/MethodDescExtensions.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/TypeSystem/MethodDescExtensions.cs @@ -21,6 +21,10 @@ public static MethodDesc GetPrimaryMethodDesc(this MethodDesc method) { return method.GetUnboxedMethod().GetPrimaryMethodDesc(); } + if (method.IsReturnDroppingAsyncThunk()) + { + return method.GetTargetOfReturnDroppingAsyncThunk().GetPrimaryMethodDesc(); + } return method switch { PInvokeTargetNativeMethod pinvokeTarget => pinvokeTarget.Target, diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/TypeSystem/Mutable/MutableModule.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/TypeSystem/Mutable/MutableModule.cs index 79c0853b0d817e..e571ed1d8b3ddc 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/TypeSystem/Mutable/MutableModule.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/TypeSystem/Mutable/MutableModule.cs @@ -39,7 +39,8 @@ protected override EntityHandle GetNonNestedResolutionScope(MetadataType metadat if (!_mutableModule._moduleToModuleRefString.TryGetValue(module, out moduleRefString)) { Debug.Assert(_mutableModule.ModuleThatIsCurrentlyTheSourceOfNewReferences != null && - _mutableModule._compilationGroup.CrossModuleInlineableModule(_mutableModule.ModuleThatIsCurrentlyTheSourceOfNewReferences)); + (_mutableModule._compilationGroup.CrossModuleInlineableModule(_mutableModule.ModuleThatIsCurrentlyTheSourceOfNewReferences) + || _mutableModule.CreatingTokensForAsyncMethod)); if (module == _typeSystemContext.SystemModule) { @@ -353,6 +354,8 @@ private int GetAssemblyRefHandle(TypeSystemMetadataEmitter emitter, object name) public int ModuleTypeSort => 1; + public bool CreatingTokensForAsyncMethod { get; set; } + public int CompareTo(IEcmaModule other) { if (other == this)