From a17eae7d1cda21ec222f6029ad2067f763cf6da0 Mon Sep 17 00:00:00 2001 From: Eduardo Velarde Date: Tue, 12 May 2026 18:14:10 -0700 Subject: [PATCH 1/4] Make IL Scanner report both variants --- .../IL/ILImporter.Scanner.cs | 32 ++++++++++++++++--- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/IL/ILImporter.Scanner.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/IL/ILImporter.Scanner.cs index 5fb5421e58cc7e..baaa465dcd6bed 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/IL/ILImporter.Scanner.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/IL/ILImporter.Scanner.cs @@ -449,6 +449,8 @@ private void ImportCall(ILOpcode opcode, int token) Debug.Assert(false); break; } + MethodDesc asyncVariantMethod = null; + MethodDesc asyncVariantRuntimeDeterminedMethod = null; // Are we scanning a call within a state machine? if (opcode is ILOpcode.call or ILOpcode.callvirt && _canonMethod.IsAsyncCall()) @@ -475,8 +477,10 @@ private void ImportCall(ILOpcode opcode, int token) _dependencies.Add(_factory.MethodEntrypoint(asyncHelpers.GetKnownMethod("FinishSuspensionWithContinuationContext"u8, null)), asyncReason); } - // If this is the task await pattern, we're actually going to call the variant - // so switch our focus to the variant. + // If this is the task await pattern, the JIT will first resolve the call to the + // async variant, then may switch back to the original if the async variant is just + // a thunk (for non-runtime-async methods). Report both variants as dependencies such + // that the JIT can pick either one. // in rare cases a method that returns Task is not actually TaskReturning (i.e. returns T). // we cannot resolve to an Async variant in such case. @@ -487,8 +491,8 @@ private void ImportCall(ILOpcode opcode, int token) if (allowAsyncVariant && MatchTaskAwaitPattern()) { - runtimeDeterminedMethod = _factory.TypeSystemContext.GetAsyncVariantMethod(runtimeDeterminedMethod); - method = _factory.TypeSystemContext.GetAsyncVariantMethod(method); + asyncVariantMethod = _factory.TypeSystemContext.GetAsyncVariantMethod(method); + asyncVariantRuntimeDeterminedMethod = _factory.TypeSystemContext.GetAsyncVariantMethod(runtimeDeterminedMethod); } } @@ -812,6 +816,16 @@ private void ImportCall(ILOpcode opcode, int token) } _dependencies.Add(_factory.CanonicalEntrypoint(targetMethod), reason); + + if (asyncVariantMethod != null && _canonMethod.IsSharedByGenericInstantiations) + { + MethodDesc avTargetMethod = asyncVariantMethod.GetCanonMethodTarget(CanonicalFormKind.Specific); + if (avTargetMethod.RequiresInstMethodDescArg()) + _dependencies.Add(GetGenericLookupHelper(ReadyToRunHelperId.MethodDictionary, asyncVariantRuntimeDeterminedMethod), reason); + else if (avTargetMethod.RequiresInstMethodTableArg()) + _dependencies.Add(GetGenericLookupHelper(ReadyToRunHelperId.TypeHandle, asyncVariantRuntimeDeterminedMethod.OwningType), reason); + _dependencies.Add(_factory.CanonicalEntrypoint(avTargetMethod), reason); + } } else { @@ -853,6 +867,16 @@ private void ImportCall(ILOpcode opcode, int token) } _dependencies.Add(GetMethodEntrypoint(targetMethod), reason); + + if (asyncVariantMethod != null) + { + MethodDesc avTargetMethod = asyncVariantMethod.GetCanonMethodTarget(CanonicalFormKind.Specific); + if (avTargetMethod.RequiresInstMethodDescArg()) + _dependencies.Add(_compilation.NodeFactory.MethodGenericDictionary(asyncVariantMethod), reason); + else if (avTargetMethod.RequiresInstMethodTableArg()) + _dependencies.Add(_compilation.NodeFactory.ConstructedTypeSymbol(asyncVariantMethod.OwningType), reason); + _dependencies.Add(GetMethodEntrypoint(avTargetMethod), reason); + } } } else if (staticResolution is DefaultInterfaceMethodResolution.Diamond or DefaultInterfaceMethodResolution.Reabstraction) From db0eb555590fe9ca31e66611679a834231717404 Mon Sep 17 00:00:00 2001 From: Eduardo Velarde Date: Wed, 13 May 2026 13:29:06 -0700 Subject: [PATCH 2/4] Refactor ImportCall --- .../IL/ILImporter.Scanner.cs | 403 +++++++++--------- 1 file changed, 196 insertions(+), 207 deletions(-) diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/IL/ILImporter.Scanner.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/IL/ILImporter.Scanner.cs index baaa465dcd6bed..2d795319187c34 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/IL/ILImporter.Scanner.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/IL/ILImporter.Scanner.cs @@ -413,194 +413,8 @@ private bool MatchTaskAwaitPattern() && IsAsyncHelpersAwait((MethodDesc)_methodIL.GetObject(reader.ReadILToken())); } - private void ImportCall(ILOpcode opcode, int token) + private void ImportCall(ILOpcode opcode, string reason, MethodDesc method, MethodDesc runtimeDeterminedMethod) { - // We get both the canonical and runtime determined form - JitInterface mostly operates - // on the canonical form. - var runtimeDeterminedMethod = (MethodDesc)_methodIL.GetObject(token); - var method = (MethodDesc)_canonMethodIL.GetObject(token); - - _compilation.TypeSystemContext.EnsureLoadableMethod(method); - if ((method.Signature.Flags & MethodSignatureFlags.UnmanagedCallingConventionMask) == MethodSignatureFlags.CallingConventionVarargs) - ThrowHelper.ThrowBadImageFormatException(); - - _compilation.NodeFactory.MetadataManager.GetDependenciesDueToAccess(ref _dependencies, _compilation.NodeFactory, _canonMethodIL, method); - - if (method.IsRawPInvoke()) - { - // Raw P/invokes don't have any dependencies. - return; - } - - string reason = null; - switch (opcode) - { - case ILOpcode.newobj: - reason = "newobj"; break; - case ILOpcode.call: - reason = "call"; break; - case ILOpcode.callvirt: - reason = "callvirt"; break; - case ILOpcode.ldftn: - reason = "ldftn"; break; - case ILOpcode.ldvirtftn: - reason = "ldvirtftn"; break; - default: - Debug.Assert(false); break; - } - - MethodDesc asyncVariantMethod = null; - MethodDesc asyncVariantRuntimeDeterminedMethod = null; - // Are we scanning a call within a state machine? - if (opcode is ILOpcode.call or ILOpcode.callvirt - && _canonMethod.IsAsyncCall()) - { - // Add dependencies on infra to do suspend/resume. We only need to do this once per method scanned. - if (!_asyncDependenciesReported && method.IsAsync) - { - _asyncDependenciesReported = true; - - const string asyncReason = "Async state machine"; - - AsyncResumptionStub resumptionStub = _compilation.TypeSystemContext.GetAsyncResumptionStub(_canonMethod, _compilation.TypeSystemContext.GeneratedAssembly.GetGlobalModuleType()); - _dependencies.Add(_compilation.NodeFactory.MethodEntrypoint(resumptionStub), asyncReason); - - _dependencies.Add(_factory.ConstructedTypeSymbol(_compilation.TypeSystemContext.ContinuationType), asyncReason); - - DefType asyncHelpers = _compilation.TypeSystemContext.SystemModule.GetKnownType("System.Runtime.CompilerServices"u8, "AsyncHelpers"u8); - - _dependencies.Add(_compilation.GetHelperEntrypoint(ReadyToRunHelper.AllocContinuation), asyncReason); - _dependencies.Add(_factory.MethodEntrypoint(asyncHelpers.GetKnownMethod("CaptureExecutionContext"u8, null)), asyncReason); - _dependencies.Add(_factory.MethodEntrypoint(asyncHelpers.GetKnownMethod("CaptureContinuationContext"u8, null)), asyncReason); - _dependencies.Add(_factory.MethodEntrypoint(asyncHelpers.GetKnownMethod("RestoreContextsOnSuspension"u8, null)), asyncReason); - _dependencies.Add(_factory.MethodEntrypoint(asyncHelpers.GetKnownMethod("FinishSuspensionNoContinuationContext"u8, null)), asyncReason); - _dependencies.Add(_factory.MethodEntrypoint(asyncHelpers.GetKnownMethod("FinishSuspensionWithContinuationContext"u8, null)), asyncReason); - } - - // If this is the task await pattern, the JIT will first resolve the call to the - // async variant, then may switch back to the original if the async variant is just - // a thunk (for non-runtime-async methods). Report both variants as dependencies such - // that the JIT can pick either one. - - // in rare cases a method that returns Task is not actually TaskReturning (i.e. returns T). - // we cannot resolve to an Async variant in such case. - bool allowAsyncVariant = method.GetTypicalMethodDefinition().Signature.ReturnsTaskOrValueTask(); - - // Don't get async variant of Delegate.Invoke method; the pointed to method is not an async variant either. - allowAsyncVariant = allowAsyncVariant && !method.OwningType.IsDelegate; - - if (allowAsyncVariant && MatchTaskAwaitPattern()) - { - asyncVariantMethod = _factory.TypeSystemContext.GetAsyncVariantMethod(method); - asyncVariantRuntimeDeterminedMethod = _factory.TypeSystemContext.GetAsyncVariantMethod(runtimeDeterminedMethod); - } - } - - if (opcode == ILOpcode.newobj) - { - TypeDesc owningType = runtimeDeterminedMethod.OwningType; - if (owningType.IsString) - { - // String .ctor handled specially below - } - else if (owningType.IsGCPointer) - { - if (owningType.IsRuntimeDeterminedSubtype) - { - _dependencies.Add(GetGenericLookupHelper(ReadyToRunHelperId.TypeHandle, owningType), reason); - } - else - { - _dependencies.Add(_factory.ConstructedTypeSymbol(owningType), reason); - } - - if (owningType.IsArray) - { - // RyuJIT is going to call the "MdArray" creation helper even if this is an SzArray, - // hence the IsArray check above. Note that the MdArray helper can handle SzArrays. - if (((ArrayType)owningType).Rank == 1) - _dependencies.Add(GetHelperEntrypoint(ReadyToRunHelper.NewMultiDimArrRare), reason); - else - _dependencies.Add(GetHelperEntrypoint(ReadyToRunHelper.NewMultiDimArr), reason); - return; - } - else - { - _dependencies.Add(GetHelperEntrypoint(ReadyToRunHelper.NewObject), reason); - } - } - } - - if (method.OwningType.IsDelegate && method.Name.SequenceEqual("Invoke"u8) && - opcode != ILOpcode.ldftn && opcode != ILOpcode.ldvirtftn) - { - // This call is expanded as an intrinsic; it's not an actual function call. - // Before codegen realizes this is an intrinsic, it might still ask questions about - // the vtable of this virtual method, so let's make sure it's marked in the scanner's - // dependency graph. - _dependencies.Add(_factory.VTable(method.OwningType), reason); - return; - } - - if (method.IsIntrinsic) - { - if (IsActivatorDefaultConstructorOf(method)) - { - if (runtimeDeterminedMethod.IsRuntimeDeterminedExactMethod) - { - _dependencies.Add(GetGenericLookupHelper(ReadyToRunHelperId.DefaultConstructor, runtimeDeterminedMethod.Instantiation[0]), reason); - } - else - { - TypeDesc type = method.Instantiation[0]; - MethodDesc ctor = Compilation.GetConstructorForCreateInstanceIntrinsic(type); - _dependencies.Add(type.IsValueType ? _factory.ExactCallableAddress(ctor) : _factory.CanonicalEntrypoint(ctor), reason); - } - - return; - } - - if (IsActivatorAllocatorOf(method)) - { - if (runtimeDeterminedMethod.IsRuntimeDeterminedExactMethod) - { - _dependencies.Add(GetGenericLookupHelper(ReadyToRunHelperId.ObjectAllocator, runtimeDeterminedMethod.Instantiation[0]), reason); - } - else - { - _dependencies.Add(_compilation.ComputeConstantLookup(ReadyToRunHelperId.ObjectAllocator, method.Instantiation[0]), reason); - } - - return; - } - - if (IsEETypePtrOf(method)) - { - if (runtimeDeterminedMethod.IsRuntimeDeterminedExactMethod) - { - _dependencies.Add(GetGenericLookupHelper(ReadyToRunHelperId.TypeHandle, runtimeDeterminedMethod.Instantiation[0]), reason); - } - else - { - _dependencies.Add(_factory.ConstructedTypeSymbol(method.Instantiation[0]), reason); - } - return; - } - - if (opcode != ILOpcode.ldftn) - { - if (IsRuntimeHelpersIsReferenceOrContainsReferences(method)) - { - return; - } - - if (IsMemoryMarshalGetArrayDataReference(method)) - { - return; - } - } - } - TypeDesc exactType = method.OwningType; bool resolvedConstraint = false; @@ -816,16 +630,6 @@ private void ImportCall(ILOpcode opcode, int token) } _dependencies.Add(_factory.CanonicalEntrypoint(targetMethod), reason); - - if (asyncVariantMethod != null && _canonMethod.IsSharedByGenericInstantiations) - { - MethodDesc avTargetMethod = asyncVariantMethod.GetCanonMethodTarget(CanonicalFormKind.Specific); - if (avTargetMethod.RequiresInstMethodDescArg()) - _dependencies.Add(GetGenericLookupHelper(ReadyToRunHelperId.MethodDictionary, asyncVariantRuntimeDeterminedMethod), reason); - else if (avTargetMethod.RequiresInstMethodTableArg()) - _dependencies.Add(GetGenericLookupHelper(ReadyToRunHelperId.TypeHandle, asyncVariantRuntimeDeterminedMethod.OwningType), reason); - _dependencies.Add(_factory.CanonicalEntrypoint(avTargetMethod), reason); - } } else { @@ -867,16 +671,6 @@ private void ImportCall(ILOpcode opcode, int token) } _dependencies.Add(GetMethodEntrypoint(targetMethod), reason); - - if (asyncVariantMethod != null) - { - MethodDesc avTargetMethod = asyncVariantMethod.GetCanonMethodTarget(CanonicalFormKind.Specific); - if (avTargetMethod.RequiresInstMethodDescArg()) - _dependencies.Add(_compilation.NodeFactory.MethodGenericDictionary(asyncVariantMethod), reason); - else if (avTargetMethod.RequiresInstMethodTableArg()) - _dependencies.Add(_compilation.NodeFactory.ConstructedTypeSymbol(asyncVariantMethod.OwningType), reason); - _dependencies.Add(GetMethodEntrypoint(avTargetMethod), reason); - } } } else if (staticResolution is DefaultInterfaceMethodResolution.Diamond or DefaultInterfaceMethodResolution.Reabstraction) @@ -969,6 +763,201 @@ private void ImportCall(ILOpcode opcode, int token) } } + private void ImportCall(ILOpcode opcode, int token) + { + // We get both the canonical and runtime determined form - JitInterface mostly operates + // on the canonical form. + var runtimeDeterminedMethod = (MethodDesc)_methodIL.GetObject(token); + var method = (MethodDesc)_canonMethodIL.GetObject(token); + + _compilation.TypeSystemContext.EnsureLoadableMethod(method); + if ((method.Signature.Flags & MethodSignatureFlags.UnmanagedCallingConventionMask) == MethodSignatureFlags.CallingConventionVarargs) + ThrowHelper.ThrowBadImageFormatException(); + + _compilation.NodeFactory.MetadataManager.GetDependenciesDueToAccess(ref _dependencies, _compilation.NodeFactory, _canonMethodIL, method); + + if (method.IsRawPInvoke()) + { + // Raw P/invokes don't have any dependencies. + return; + } + + string reason = null; + switch (opcode) + { + case ILOpcode.newobj: + reason = "newobj"; break; + case ILOpcode.call: + reason = "call"; break; + case ILOpcode.callvirt: + reason = "callvirt"; break; + case ILOpcode.ldftn: + reason = "ldftn"; break; + case ILOpcode.ldvirtftn: + reason = "ldvirtftn"; break; + default: + Debug.Assert(false); break; + } + + MethodDesc asyncVariantMethod = null; + MethodDesc asyncVariantRuntimeDeterminedMethod = null; + // Are we scanning a call within a state machine? + if (opcode is ILOpcode.call or ILOpcode.callvirt + && _canonMethod.IsAsyncCall()) + { + // Add dependencies on infra to do suspend/resume. We only need to do this once per method scanned. + if (!_asyncDependenciesReported && method.IsAsync) + { + _asyncDependenciesReported = true; + + const string asyncReason = "Async state machine"; + + AsyncResumptionStub resumptionStub = _compilation.TypeSystemContext.GetAsyncResumptionStub(_canonMethod, _compilation.TypeSystemContext.GeneratedAssembly.GetGlobalModuleType()); + _dependencies.Add(_compilation.NodeFactory.MethodEntrypoint(resumptionStub), asyncReason); + + _dependencies.Add(_factory.ConstructedTypeSymbol(_compilation.TypeSystemContext.ContinuationType), asyncReason); + + DefType asyncHelpers = _compilation.TypeSystemContext.SystemModule.GetKnownType("System.Runtime.CompilerServices"u8, "AsyncHelpers"u8); + + _dependencies.Add(_compilation.GetHelperEntrypoint(ReadyToRunHelper.AllocContinuation), asyncReason); + _dependencies.Add(_factory.MethodEntrypoint(asyncHelpers.GetKnownMethod("CaptureExecutionContext"u8, null)), asyncReason); + _dependencies.Add(_factory.MethodEntrypoint(asyncHelpers.GetKnownMethod("CaptureContinuationContext"u8, null)), asyncReason); + _dependencies.Add(_factory.MethodEntrypoint(asyncHelpers.GetKnownMethod("RestoreContextsOnSuspension"u8, null)), asyncReason); + _dependencies.Add(_factory.MethodEntrypoint(asyncHelpers.GetKnownMethod("FinishSuspensionNoContinuationContext"u8, null)), asyncReason); + _dependencies.Add(_factory.MethodEntrypoint(asyncHelpers.GetKnownMethod("FinishSuspensionWithContinuationContext"u8, null)), asyncReason); + } + + // If this is the task await pattern, the JIT will first resolve the call to the + // async variant, then may switch back to the original if the async variant is just + // a thunk (for non-runtime-async methods). Report both variants as dependencies such + // that the JIT can pick either one. + + // in rare cases a method that returns Task is not actually TaskReturning (i.e. returns T). + // we cannot resolve to an Async variant in such case. + bool allowAsyncVariant = method.GetTypicalMethodDefinition().Signature.ReturnsTaskOrValueTask(); + + // Don't get async variant of Delegate.Invoke method; the pointed to method is not an async variant either. + allowAsyncVariant = allowAsyncVariant && !method.OwningType.IsDelegate; + + if (allowAsyncVariant && MatchTaskAwaitPattern()) + { + asyncVariantMethod = _factory.TypeSystemContext.GetAsyncVariantMethod(method); + asyncVariantRuntimeDeterminedMethod = _factory.TypeSystemContext.GetAsyncVariantMethod(runtimeDeterminedMethod); + } + } + + if (opcode == ILOpcode.newobj) + { + TypeDesc owningType = runtimeDeterminedMethod.OwningType; + if (owningType.IsString) + { + // String .ctor handled specially below + } + else if (owningType.IsGCPointer) + { + if (owningType.IsRuntimeDeterminedSubtype) + { + _dependencies.Add(GetGenericLookupHelper(ReadyToRunHelperId.TypeHandle, owningType), reason); + } + else + { + _dependencies.Add(_factory.ConstructedTypeSymbol(owningType), reason); + } + + if (owningType.IsArray) + { + // RyuJIT is going to call the "MdArray" creation helper even if this is an SzArray, + // hence the IsArray check above. Note that the MdArray helper can handle SzArrays. + if (((ArrayType)owningType).Rank == 1) + _dependencies.Add(GetHelperEntrypoint(ReadyToRunHelper.NewMultiDimArrRare), reason); + else + _dependencies.Add(GetHelperEntrypoint(ReadyToRunHelper.NewMultiDimArr), reason); + return; + } + else + { + _dependencies.Add(GetHelperEntrypoint(ReadyToRunHelper.NewObject), reason); + } + } + } + + if (method.OwningType.IsDelegate && method.Name.SequenceEqual("Invoke"u8) && + opcode != ILOpcode.ldftn && opcode != ILOpcode.ldvirtftn) + { + // This call is expanded as an intrinsic; it's not an actual function call. + // Before codegen realizes this is an intrinsic, it might still ask questions about + // the vtable of this virtual method, so let's make sure it's marked in the scanner's + // dependency graph. + _dependencies.Add(_factory.VTable(method.OwningType), reason); + return; + } + + if (method.IsIntrinsic) + { + if (IsActivatorDefaultConstructorOf(method)) + { + if (runtimeDeterminedMethod.IsRuntimeDeterminedExactMethod) + { + _dependencies.Add(GetGenericLookupHelper(ReadyToRunHelperId.DefaultConstructor, runtimeDeterminedMethod.Instantiation[0]), reason); + } + else + { + TypeDesc type = method.Instantiation[0]; + MethodDesc ctor = Compilation.GetConstructorForCreateInstanceIntrinsic(type); + _dependencies.Add(type.IsValueType ? _factory.ExactCallableAddress(ctor) : _factory.CanonicalEntrypoint(ctor), reason); + } + + return; + } + + if (IsActivatorAllocatorOf(method)) + { + if (runtimeDeterminedMethod.IsRuntimeDeterminedExactMethod) + { + _dependencies.Add(GetGenericLookupHelper(ReadyToRunHelperId.ObjectAllocator, runtimeDeterminedMethod.Instantiation[0]), reason); + } + else + { + _dependencies.Add(_compilation.ComputeConstantLookup(ReadyToRunHelperId.ObjectAllocator, method.Instantiation[0]), reason); + } + + return; + } + + if (IsEETypePtrOf(method)) + { + if (runtimeDeterminedMethod.IsRuntimeDeterminedExactMethod) + { + _dependencies.Add(GetGenericLookupHelper(ReadyToRunHelperId.TypeHandle, runtimeDeterminedMethod.Instantiation[0]), reason); + } + else + { + _dependencies.Add(_factory.ConstructedTypeSymbol(method.Instantiation[0]), reason); + } + return; + } + + if (opcode != ILOpcode.ldftn) + { + if (IsRuntimeHelpersIsReferenceOrContainsReferences(method)) + { + return; + } + + if (IsMemoryMarshalGetArrayDataReference(method)) + { + return; + } + } + } + + ImportCall(opcode, reason, method, runtimeDeterminedMethod); + if (asyncVariantMethod != null) + { + ImportCall(opcode, reason, asyncVariantMethod, asyncVariantRuntimeDeterminedMethod); + } + } + private void ImportLdFtn(int token, ILOpcode opCode) { ImportCall(opCode, token); From ad95ad2481f8857baba8b39ffe38087cd0fa4385 Mon Sep 17 00:00:00 2001 From: Eduardo Velarde Date: Wed, 13 May 2026 14:17:26 -0700 Subject: [PATCH 3/4] Adjust refactor --- .../IL/ILImporter.Scanner.cs | 390 +++++++++--------- 1 file changed, 195 insertions(+), 195 deletions(-) diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/IL/ILImporter.Scanner.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/IL/ILImporter.Scanner.cs index 2d795319187c34..a8bc42d80dac45 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/IL/ILImporter.Scanner.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/IL/ILImporter.Scanner.cs @@ -413,8 +413,203 @@ private bool MatchTaskAwaitPattern() && IsAsyncHelpersAwait((MethodDesc)_methodIL.GetObject(reader.ReadILToken())); } + private void ImportCall(ILOpcode opcode, int token) + { + // We get both the canonical and runtime determined form - JitInterface mostly operates + // on the canonical form. + var runtimeDeterminedMethod = (MethodDesc)_methodIL.GetObject(token); + var method = (MethodDesc)_canonMethodIL.GetObject(token); + + _compilation.TypeSystemContext.EnsureLoadableMethod(method); + if ((method.Signature.Flags & MethodSignatureFlags.UnmanagedCallingConventionMask) == MethodSignatureFlags.CallingConventionVarargs) + ThrowHelper.ThrowBadImageFormatException(); + + _compilation.NodeFactory.MetadataManager.GetDependenciesDueToAccess(ref _dependencies, _compilation.NodeFactory, _canonMethodIL, method); + + if (method.IsRawPInvoke()) + { + // Raw P/invokes don't have any dependencies. + return; + } + + string reason = null; + switch (opcode) + { + case ILOpcode.newobj: + reason = "newobj"; break; + case ILOpcode.call: + reason = "call"; break; + case ILOpcode.callvirt: + reason = "callvirt"; break; + case ILOpcode.ldftn: + reason = "ldftn"; break; + case ILOpcode.ldvirtftn: + reason = "ldvirtftn"; break; + default: + Debug.Assert(false); break; + } + + MethodDesc asyncVariantMethod = null; + MethodDesc asyncVariantRuntimeDeterminedMethod = null; + // Are we scanning a call within a state machine? + if (opcode is ILOpcode.call or ILOpcode.callvirt + && _canonMethod.IsAsyncCall()) + { + // Add dependencies on infra to do suspend/resume. We only need to do this once per method scanned. + if (!_asyncDependenciesReported && method.IsAsync) + { + _asyncDependenciesReported = true; + + const string asyncReason = "Async state machine"; + + AsyncResumptionStub resumptionStub = _compilation.TypeSystemContext.GetAsyncResumptionStub(_canonMethod, _compilation.TypeSystemContext.GeneratedAssembly.GetGlobalModuleType()); + _dependencies.Add(_compilation.NodeFactory.MethodEntrypoint(resumptionStub), asyncReason); + + _dependencies.Add(_factory.ConstructedTypeSymbol(_compilation.TypeSystemContext.ContinuationType), asyncReason); + + DefType asyncHelpers = _compilation.TypeSystemContext.SystemModule.GetKnownType("System.Runtime.CompilerServices"u8, "AsyncHelpers"u8); + + _dependencies.Add(_compilation.GetHelperEntrypoint(ReadyToRunHelper.AllocContinuation), asyncReason); + _dependencies.Add(_factory.MethodEntrypoint(asyncHelpers.GetKnownMethod("CaptureExecutionContext"u8, null)), asyncReason); + _dependencies.Add(_factory.MethodEntrypoint(asyncHelpers.GetKnownMethod("CaptureContinuationContext"u8, null)), asyncReason); + _dependencies.Add(_factory.MethodEntrypoint(asyncHelpers.GetKnownMethod("RestoreContextsOnSuspension"u8, null)), asyncReason); + _dependencies.Add(_factory.MethodEntrypoint(asyncHelpers.GetKnownMethod("FinishSuspensionNoContinuationContext"u8, null)), asyncReason); + _dependencies.Add(_factory.MethodEntrypoint(asyncHelpers.GetKnownMethod("FinishSuspensionWithContinuationContext"u8, null)), asyncReason); + } + + // If this is the task await pattern, the JIT will first resolve the call to the + // async variant, then may switch back to the original if the async variant is just + // a thunk (for non-runtime-async methods). Report both variants as dependencies such + // that the JIT can pick either one. + + // in rare cases a method that returns Task is not actually TaskReturning (i.e. returns T). + // we cannot resolve to an Async variant in such case. + bool allowAsyncVariant = method.GetTypicalMethodDefinition().Signature.ReturnsTaskOrValueTask(); + + // Don't get async variant of Delegate.Invoke method; the pointed to method is not an async variant either. + allowAsyncVariant = allowAsyncVariant && !method.OwningType.IsDelegate; + + if (allowAsyncVariant && MatchTaskAwaitPattern()) + { + asyncVariantMethod = _factory.TypeSystemContext.GetAsyncVariantMethod(method); + asyncVariantRuntimeDeterminedMethod = _factory.TypeSystemContext.GetAsyncVariantMethod(runtimeDeterminedMethod); + } + } + + ImportCall(opcode, reason, method, runtimeDeterminedMethod); + if (asyncVariantMethod != null) + { + ImportCall(opcode, reason, asyncVariantMethod, asyncVariantRuntimeDeterminedMethod); + } + } + private void ImportCall(ILOpcode opcode, string reason, MethodDesc method, MethodDesc runtimeDeterminedMethod) { + if (opcode == ILOpcode.newobj) + { + TypeDesc owningType = runtimeDeterminedMethod.OwningType; + if (owningType.IsString) + { + // String .ctor handled specially below + } + else if (owningType.IsGCPointer) + { + if (owningType.IsRuntimeDeterminedSubtype) + { + _dependencies.Add(GetGenericLookupHelper(ReadyToRunHelperId.TypeHandle, owningType), reason); + } + else + { + _dependencies.Add(_factory.ConstructedTypeSymbol(owningType), reason); + } + + if (owningType.IsArray) + { + // RyuJIT is going to call the "MdArray" creation helper even if this is an SzArray, + // hence the IsArray check above. Note that the MdArray helper can handle SzArrays. + if (((ArrayType)owningType).Rank == 1) + _dependencies.Add(GetHelperEntrypoint(ReadyToRunHelper.NewMultiDimArrRare), reason); + else + _dependencies.Add(GetHelperEntrypoint(ReadyToRunHelper.NewMultiDimArr), reason); + return; + } + else + { + _dependencies.Add(GetHelperEntrypoint(ReadyToRunHelper.NewObject), reason); + } + } + } + + if (method.OwningType.IsDelegate && method.Name.SequenceEqual("Invoke"u8) && + opcode != ILOpcode.ldftn && opcode != ILOpcode.ldvirtftn) + { + // This call is expanded as an intrinsic; it's not an actual function call. + // Before codegen realizes this is an intrinsic, it might still ask questions about + // the vtable of this virtual method, so let's make sure it's marked in the scanner's + // dependency graph. + _dependencies.Add(_factory.VTable(method.OwningType), reason); + return; + } + + if (method.IsIntrinsic) + { + if (IsActivatorDefaultConstructorOf(method)) + { + if (runtimeDeterminedMethod.IsRuntimeDeterminedExactMethod) + { + _dependencies.Add(GetGenericLookupHelper(ReadyToRunHelperId.DefaultConstructor, runtimeDeterminedMethod.Instantiation[0]), reason); + } + else + { + TypeDesc type = method.Instantiation[0]; + MethodDesc ctor = Compilation.GetConstructorForCreateInstanceIntrinsic(type); + _dependencies.Add(type.IsValueType ? _factory.ExactCallableAddress(ctor) : _factory.CanonicalEntrypoint(ctor), reason); + } + + return; + } + + if (IsActivatorAllocatorOf(method)) + { + if (runtimeDeterminedMethod.IsRuntimeDeterminedExactMethod) + { + _dependencies.Add(GetGenericLookupHelper(ReadyToRunHelperId.ObjectAllocator, runtimeDeterminedMethod.Instantiation[0]), reason); + } + else + { + _dependencies.Add(_compilation.ComputeConstantLookup(ReadyToRunHelperId.ObjectAllocator, method.Instantiation[0]), reason); + } + + return; + } + + if (IsEETypePtrOf(method)) + { + if (runtimeDeterminedMethod.IsRuntimeDeterminedExactMethod) + { + _dependencies.Add(GetGenericLookupHelper(ReadyToRunHelperId.TypeHandle, runtimeDeterminedMethod.Instantiation[0]), reason); + } + else + { + _dependencies.Add(_factory.ConstructedTypeSymbol(method.Instantiation[0]), reason); + } + return; + } + + if (opcode != ILOpcode.ldftn) + { + if (IsRuntimeHelpersIsReferenceOrContainsReferences(method)) + { + return; + } + + if (IsMemoryMarshalGetArrayDataReference(method)) + { + return; + } + } + } + TypeDesc exactType = method.OwningType; bool resolvedConstraint = false; @@ -763,201 +958,6 @@ private void ImportCall(ILOpcode opcode, string reason, MethodDesc method, Metho } } - private void ImportCall(ILOpcode opcode, int token) - { - // We get both the canonical and runtime determined form - JitInterface mostly operates - // on the canonical form. - var runtimeDeterminedMethod = (MethodDesc)_methodIL.GetObject(token); - var method = (MethodDesc)_canonMethodIL.GetObject(token); - - _compilation.TypeSystemContext.EnsureLoadableMethod(method); - if ((method.Signature.Flags & MethodSignatureFlags.UnmanagedCallingConventionMask) == MethodSignatureFlags.CallingConventionVarargs) - ThrowHelper.ThrowBadImageFormatException(); - - _compilation.NodeFactory.MetadataManager.GetDependenciesDueToAccess(ref _dependencies, _compilation.NodeFactory, _canonMethodIL, method); - - if (method.IsRawPInvoke()) - { - // Raw P/invokes don't have any dependencies. - return; - } - - string reason = null; - switch (opcode) - { - case ILOpcode.newobj: - reason = "newobj"; break; - case ILOpcode.call: - reason = "call"; break; - case ILOpcode.callvirt: - reason = "callvirt"; break; - case ILOpcode.ldftn: - reason = "ldftn"; break; - case ILOpcode.ldvirtftn: - reason = "ldvirtftn"; break; - default: - Debug.Assert(false); break; - } - - MethodDesc asyncVariantMethod = null; - MethodDesc asyncVariantRuntimeDeterminedMethod = null; - // Are we scanning a call within a state machine? - if (opcode is ILOpcode.call or ILOpcode.callvirt - && _canonMethod.IsAsyncCall()) - { - // Add dependencies on infra to do suspend/resume. We only need to do this once per method scanned. - if (!_asyncDependenciesReported && method.IsAsync) - { - _asyncDependenciesReported = true; - - const string asyncReason = "Async state machine"; - - AsyncResumptionStub resumptionStub = _compilation.TypeSystemContext.GetAsyncResumptionStub(_canonMethod, _compilation.TypeSystemContext.GeneratedAssembly.GetGlobalModuleType()); - _dependencies.Add(_compilation.NodeFactory.MethodEntrypoint(resumptionStub), asyncReason); - - _dependencies.Add(_factory.ConstructedTypeSymbol(_compilation.TypeSystemContext.ContinuationType), asyncReason); - - DefType asyncHelpers = _compilation.TypeSystemContext.SystemModule.GetKnownType("System.Runtime.CompilerServices"u8, "AsyncHelpers"u8); - - _dependencies.Add(_compilation.GetHelperEntrypoint(ReadyToRunHelper.AllocContinuation), asyncReason); - _dependencies.Add(_factory.MethodEntrypoint(asyncHelpers.GetKnownMethod("CaptureExecutionContext"u8, null)), asyncReason); - _dependencies.Add(_factory.MethodEntrypoint(asyncHelpers.GetKnownMethod("CaptureContinuationContext"u8, null)), asyncReason); - _dependencies.Add(_factory.MethodEntrypoint(asyncHelpers.GetKnownMethod("RestoreContextsOnSuspension"u8, null)), asyncReason); - _dependencies.Add(_factory.MethodEntrypoint(asyncHelpers.GetKnownMethod("FinishSuspensionNoContinuationContext"u8, null)), asyncReason); - _dependencies.Add(_factory.MethodEntrypoint(asyncHelpers.GetKnownMethod("FinishSuspensionWithContinuationContext"u8, null)), asyncReason); - } - - // If this is the task await pattern, the JIT will first resolve the call to the - // async variant, then may switch back to the original if the async variant is just - // a thunk (for non-runtime-async methods). Report both variants as dependencies such - // that the JIT can pick either one. - - // in rare cases a method that returns Task is not actually TaskReturning (i.e. returns T). - // we cannot resolve to an Async variant in such case. - bool allowAsyncVariant = method.GetTypicalMethodDefinition().Signature.ReturnsTaskOrValueTask(); - - // Don't get async variant of Delegate.Invoke method; the pointed to method is not an async variant either. - allowAsyncVariant = allowAsyncVariant && !method.OwningType.IsDelegate; - - if (allowAsyncVariant && MatchTaskAwaitPattern()) - { - asyncVariantMethod = _factory.TypeSystemContext.GetAsyncVariantMethod(method); - asyncVariantRuntimeDeterminedMethod = _factory.TypeSystemContext.GetAsyncVariantMethod(runtimeDeterminedMethod); - } - } - - if (opcode == ILOpcode.newobj) - { - TypeDesc owningType = runtimeDeterminedMethod.OwningType; - if (owningType.IsString) - { - // String .ctor handled specially below - } - else if (owningType.IsGCPointer) - { - if (owningType.IsRuntimeDeterminedSubtype) - { - _dependencies.Add(GetGenericLookupHelper(ReadyToRunHelperId.TypeHandle, owningType), reason); - } - else - { - _dependencies.Add(_factory.ConstructedTypeSymbol(owningType), reason); - } - - if (owningType.IsArray) - { - // RyuJIT is going to call the "MdArray" creation helper even if this is an SzArray, - // hence the IsArray check above. Note that the MdArray helper can handle SzArrays. - if (((ArrayType)owningType).Rank == 1) - _dependencies.Add(GetHelperEntrypoint(ReadyToRunHelper.NewMultiDimArrRare), reason); - else - _dependencies.Add(GetHelperEntrypoint(ReadyToRunHelper.NewMultiDimArr), reason); - return; - } - else - { - _dependencies.Add(GetHelperEntrypoint(ReadyToRunHelper.NewObject), reason); - } - } - } - - if (method.OwningType.IsDelegate && method.Name.SequenceEqual("Invoke"u8) && - opcode != ILOpcode.ldftn && opcode != ILOpcode.ldvirtftn) - { - // This call is expanded as an intrinsic; it's not an actual function call. - // Before codegen realizes this is an intrinsic, it might still ask questions about - // the vtable of this virtual method, so let's make sure it's marked in the scanner's - // dependency graph. - _dependencies.Add(_factory.VTable(method.OwningType), reason); - return; - } - - if (method.IsIntrinsic) - { - if (IsActivatorDefaultConstructorOf(method)) - { - if (runtimeDeterminedMethod.IsRuntimeDeterminedExactMethod) - { - _dependencies.Add(GetGenericLookupHelper(ReadyToRunHelperId.DefaultConstructor, runtimeDeterminedMethod.Instantiation[0]), reason); - } - else - { - TypeDesc type = method.Instantiation[0]; - MethodDesc ctor = Compilation.GetConstructorForCreateInstanceIntrinsic(type); - _dependencies.Add(type.IsValueType ? _factory.ExactCallableAddress(ctor) : _factory.CanonicalEntrypoint(ctor), reason); - } - - return; - } - - if (IsActivatorAllocatorOf(method)) - { - if (runtimeDeterminedMethod.IsRuntimeDeterminedExactMethod) - { - _dependencies.Add(GetGenericLookupHelper(ReadyToRunHelperId.ObjectAllocator, runtimeDeterminedMethod.Instantiation[0]), reason); - } - else - { - _dependencies.Add(_compilation.ComputeConstantLookup(ReadyToRunHelperId.ObjectAllocator, method.Instantiation[0]), reason); - } - - return; - } - - if (IsEETypePtrOf(method)) - { - if (runtimeDeterminedMethod.IsRuntimeDeterminedExactMethod) - { - _dependencies.Add(GetGenericLookupHelper(ReadyToRunHelperId.TypeHandle, runtimeDeterminedMethod.Instantiation[0]), reason); - } - else - { - _dependencies.Add(_factory.ConstructedTypeSymbol(method.Instantiation[0]), reason); - } - return; - } - - if (opcode != ILOpcode.ldftn) - { - if (IsRuntimeHelpersIsReferenceOrContainsReferences(method)) - { - return; - } - - if (IsMemoryMarshalGetArrayDataReference(method)) - { - return; - } - } - } - - ImportCall(opcode, reason, method, runtimeDeterminedMethod); - if (asyncVariantMethod != null) - { - ImportCall(opcode, reason, asyncVariantMethod, asyncVariantRuntimeDeterminedMethod); - } - } - private void ImportLdFtn(int token, ILOpcode opCode) { ImportCall(opCode, token); From 0dde1ccc1629c84d3d35e1a488721cb96bb837f8 Mon Sep 17 00:00:00 2001 From: Eduardo Velarde Date: Wed, 13 May 2026 14:26:31 -0700 Subject: [PATCH 4/4] Adjust refactor --- .../IL/ILImporter.Scanner.cs | 40 +++++++++---------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/IL/ILImporter.Scanner.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/IL/ILImporter.Scanner.cs index a8bc42d80dac45..eef2e276c6d164 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/IL/ILImporter.Scanner.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/IL/ILImporter.Scanner.cs @@ -432,23 +432,6 @@ private void ImportCall(ILOpcode opcode, int token) return; } - string reason = null; - switch (opcode) - { - case ILOpcode.newobj: - reason = "newobj"; break; - case ILOpcode.call: - reason = "call"; break; - case ILOpcode.callvirt: - reason = "callvirt"; break; - case ILOpcode.ldftn: - reason = "ldftn"; break; - case ILOpcode.ldvirtftn: - reason = "ldvirtftn"; break; - default: - Debug.Assert(false); break; - } - MethodDesc asyncVariantMethod = null; MethodDesc asyncVariantRuntimeDeterminedMethod = null; // Are we scanning a call within a state machine? @@ -496,15 +479,32 @@ private void ImportCall(ILOpcode opcode, int token) } } - ImportCall(opcode, reason, method, runtimeDeterminedMethod); + ImportCall(opcode, method, runtimeDeterminedMethod); if (asyncVariantMethod != null) { - ImportCall(opcode, reason, asyncVariantMethod, asyncVariantRuntimeDeterminedMethod); + ImportCall(opcode, asyncVariantMethod, asyncVariantRuntimeDeterminedMethod); } } - private void ImportCall(ILOpcode opcode, string reason, MethodDesc method, MethodDesc runtimeDeterminedMethod) + private void ImportCall(ILOpcode opcode, MethodDesc method, MethodDesc runtimeDeterminedMethod) { + string reason = null; + switch (opcode) + { + case ILOpcode.newobj: + reason = "newobj"; break; + case ILOpcode.call: + reason = "call"; break; + case ILOpcode.callvirt: + reason = "callvirt"; break; + case ILOpcode.ldftn: + reason = "ldftn"; break; + case ILOpcode.ldvirtftn: + reason = "ldvirtftn"; break; + default: + Debug.Assert(false); break; + } + if (opcode == ILOpcode.newobj) { TypeDesc owningType = runtimeDeterminedMethod.OwningType;