diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunExternalTypeMapNode.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunExternalTypeMapNode.cs index d02586c9093220..2ce3599a43c6b5 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunExternalTypeMapNode.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunExternalTypeMapNode.cs @@ -11,7 +11,12 @@ namespace ILCompiler.ReadyToRun { - internal class ReadyToRunExternalTypeMapNode(ModuleDesc triggeringModule, TypeDesc group, TypeMapMetadata.IExternalTypeMap map, ImportReferenceProvider importProvider) : SortableDependencyNode, IExternalTypeMapNode + internal class ReadyToRunExternalTypeMapNode( + ModuleDesc triggeringModule, + TypeDesc group, + TypeMapMetadata.IExternalTypeMap map, + ImportReferenceProvider importProvider, + bool requiresRuntimeProcessing) : SortableDependencyNode, IExternalTypeMapNode { public TypeDesc TypeMapGroup => group; @@ -40,7 +45,7 @@ public override int CompareToImpl(ISortableNode other, CompilerComparer comparer public Vertex CreateTypeMap(NodeFactory factory, NativeWriter writer, Section section, INativeFormatTypeReferenceProvider externalReferences) { Vertex typeMapGroupVertex = externalReferences.EncodeReferenceToType(writer, TypeMapGroup, TriggeringModule); - if (map.ThrowingMethodStub is not null) + if (map.ThrowingMethodStub is not null || requiresRuntimeProcessing) { // We don't write out the throwing method stub for R2R // as emitting loose methods is not supported/very expensive. @@ -71,7 +76,7 @@ public override IEnumerable GetStaticDependencies(NodeFacto { yield return new DependencyListEntry(importProvider.GetImportToType(TypeMapGroup, TriggeringModule), $"Type map '{TypeMapGroup}' key type"); - if (map.ThrowingMethodStub is not null) + if (map.ThrowingMethodStub is not null || requiresRuntimeProcessing) { yield break; } diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunProxyTypeMapNode.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunProxyTypeMapNode.cs index 6f5e80a00f2e4b..0690b6d0450ca6 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunProxyTypeMapNode.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunProxyTypeMapNode.cs @@ -9,7 +9,12 @@ namespace ILCompiler.ReadyToRun { - internal class ReadyToRunProxyTypeMapNode(ModuleDesc triggeringModule, TypeDesc group, TypeMapMetadata.IProxyTypeMap map, ImportReferenceProvider importProvider) : SortableDependencyNode, IProxyTypeMapNode + internal class ReadyToRunProxyTypeMapNode( + ModuleDesc triggeringModule, + TypeDesc group, + TypeMapMetadata.IProxyTypeMap map, + ImportReferenceProvider importProvider, + bool requiresRuntimeProcessing) : SortableDependencyNode, IProxyTypeMapNode { public TypeDesc TypeMapGroup => group; @@ -38,7 +43,7 @@ public override int CompareToImpl(ISortableNode other, CompilerComparer comparer public Vertex CreateTypeMap(NodeFactory factory, NativeWriter writer, Section section, INativeFormatTypeReferenceProvider ProxyReferences) { Vertex typeMapGroupVertex = ProxyReferences.EncodeReferenceToType(writer, TypeMapGroup, TriggeringModule); - if (map.ThrowingMethodStub is not null) + if (map.ThrowingMethodStub is not null || requiresRuntimeProcessing) { // We don't write out the throwing method stub for R2R // as emitting loose methods is not supported/very expensive. @@ -71,7 +76,7 @@ public override IEnumerable GetStaticDependencies(NodeFacto { yield return new DependencyListEntry(importProvider.GetImportToType(TypeMapGroup, TriggeringModule), $"Type map '{TypeMapGroup}' key type"); - if (map.ThrowingMethodStub is not null) + if (map.ThrowingMethodStub is not null || requiresRuntimeProcessing) { yield break; } diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunTypeMapManager.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunTypeMapManager.cs index 60d36da324de64..ea38ebe4c315d9 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunTypeMapManager.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunTypeMapManager.cs @@ -2,22 +2,30 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Collections.Generic; +using System.Diagnostics; using ILCompiler.DependencyAnalysis; using ILCompiler.DependencyAnalysis.ReadyToRun; using ILCompiler.DependencyAnalysisFramework; +using ILCompiler.ReadyToRun.TypeSystem; using Internal.Runtime; using Internal.TypeSystem; +using Internal.TypeSystem.Ecma; namespace ILCompiler.ReadyToRun { public sealed class ReadyToRunTypeMapManager(ModuleDesc triggeringModule, TypeMapMetadata assemblyTypeMaps) : TypeMapManager { private ImportReferenceProvider _importReferenceProvider; + private readonly HashSet _externalTypeMapsRequiringRuntimeProcessing = []; + private readonly HashSet _proxyTypeMapsRequiringRuntimeProcessing = []; public override ModuleDesc AssociatedModule => triggeringModule; public override void AttachToDependencyGraph(DependencyAnalyzerBase graph) { + if (IsEmpty) + return; + base.AttachToDependencyGraph(graph); foreach (var map in GetExternalTypeMaps()) { @@ -39,7 +47,12 @@ internal override IEnumerable GetExternalTypeMaps() { foreach (var map in assemblyTypeMaps.Maps) { - yield return new ReadyToRunExternalTypeMapNode(triggeringModule, map.Key, map.Value, _importReferenceProvider); + yield return new ReadyToRunExternalTypeMapNode( + triggeringModule, + map.Key, + map.Value, + _importReferenceProvider, + _externalTypeMapsRequiringRuntimeProcessing.Contains(map.Key)); } } @@ -47,7 +60,12 @@ internal override IEnumerable GetProxyTypeMaps() { foreach (var map in assemblyTypeMaps.Maps) { - yield return new ReadyToRunProxyTypeMapNode(triggeringModule, map.Key, map.Value, _importReferenceProvider); + yield return new ReadyToRunProxyTypeMapNode( + triggeringModule, + map.Key, + map.Value, + _importReferenceProvider, + _proxyTypeMapsRequiringRuntimeProcessing.Contains(map.Key)); } } @@ -58,9 +76,108 @@ public void AddToReadyToRunHeader(ReadyToRunHeaderNode header, NodeFactory nodeF if (IsEmpty) return; + PrepareTypeMapsForEncoding(nodeFactory); + header.Add(ReadyToRunSectionType.ExternalTypeMaps, new ExternalTypeMapObjectNode(this, importReferenceProvider)); header.Add(ReadyToRunSectionType.ProxyTypeMaps, new ProxyTypeMapObjectNode(this, importReferenceProvider)); header.Add(ReadyToRunSectionType.TypeMapAssemblyTargets, new TypeMapAssemblyTargetsNode(assemblyTypeMaps, importReferenceProvider)); } + + // Some types referenced by TypeMap attributes may not have an existing TypeRef/AssemblyRef relationship to the + // module that declared the attribute. Mark just the affected (TypeMapGroup, ProxyOrExternalMap) entry for + // runtime attribute processing. + private void PrepareTypeMapsForEncoding(NodeFactory nodeFactory) + { + foreach (var mapEntry in assemblyTypeMaps.Maps) + { + TypeMapMetadata.Map map = mapEntry.Value; + + // The generic TypeMap attribute TypeSpec necessarily provides an encodable metadata + // reference to its group type. + Debug.Assert(CanEncodeReferenceToType(mapEntry.Key)); + + TypeMapMetadata.IExternalTypeMap externalTypeMap = map; + if (externalTypeMap.ThrowingMethodStub is null) + { + foreach ((TypeDesc type, _) in externalTypeMap.TypeMap.Values) + { + if (!CanEncodeReferenceToType(type)) + { + _externalTypeMapsRequiringRuntimeProcessing.Add(mapEntry.Key); + break; + } + } + } + + TypeMapMetadata.IProxyTypeMap proxyTypeMap = map; + if (proxyTypeMap.ThrowingMethodStub is null) + { + foreach (KeyValuePair typeMapEntry in proxyTypeMap.TypeMap) + { + if (!CanEncodeReferenceToType(typeMapEntry.Key) || + !CanEncodeReferenceToType(typeMapEntry.Value)) + { + _proxyTypeMapsRequiringRuntimeProcessing.Add(mapEntry.Key); + break; + } + } + } + } + + bool CanEncodeReferenceToType(TypeDesc type) + { + if (nodeFactory.CompilationModuleGroup.VersionsWithTypeReference(type)) + return true; + + if (type is EcmaType ecmaType) + { + return MutableModule.CanCreateReferenceToType( + triggeringModule, + ecmaType, + (ReadyToRunCompilationModuleGroupBase)nodeFactory.CompilationModuleGroup); + } + + if (type.IsParameterizedType) + { + return CanEncodeReferenceToType(((ParameterizedType)type).ParameterType); + } + + if (type.IsFunctionPointer) + { + MethodSignature signature = ((FunctionPointerType)type).Signature; + + if (!CanEncodeReferenceToType(signature.ReturnType)) + return false; + + for (int i = 0; i < signature.Length; i++) + { + if (!CanEncodeReferenceToType(signature[i])) + return false; + } + + return true; + } + + if (type.HasInstantiation) + { + if (!CanEncodeReferenceToType(type.GetTypeDefinition())) + return false; + + foreach (TypeDesc instantiationArgument in type.Instantiation) + { + if (!CanEncodeReferenceToType(instantiationArgument)) + return false; + } + + return true; + } + + // Generic parameters (encoded as ELEMENT_TYPE_VAR/MVAR) and other simple type + // shapes that don't reference any other type by name always encode safely. + // Anything else reaching here is an unexpected TypeDesc shape for a TypeMap + // key or value; treat it as unencodable rather than assuming successful encoding. + return type.IsSignatureVariable; + } + } } } 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 e81aaae9ffe80e..54d1026d27b826 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/TypeSystem/Mutable/MutableModule.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/TypeSystem/Mutable/MutableModule.cs @@ -124,15 +124,25 @@ static Dictionary ComputeTypeLookupTable(ModuleDesc module return result; } - static string GetNameOfAssemblyRefWhichResolvesToType(ModuleDesc module, MetadataType type) + // Looks up the AssemblyRef name used by an existing TypeRef in `moduleToSearch` that + // resolves to `referencedType` (potentially through a type-forwarder). + internal static bool TryGetAssemblyReferenceNameForTypeReference( + ModuleDesc moduleToSearch, + MetadataType referencedType, + out string assemblyReferenceName) { - if (!s_assemblyNameFromTypeLookups.TryGetValue(module, out var lookupTable)) + if (!s_assemblyNameFromTypeLookups.TryGetValue(moduleToSearch, out var lookupTable)) { - lookupTable = ComputeTypeLookupTable(module); - s_assemblyNameFromTypeLookups.AddOrUpdate(module, lookupTable); + lookupTable = ComputeTypeLookupTable(moduleToSearch); + s_assemblyNameFromTypeLookups.AddOrUpdate(moduleToSearch, lookupTable); } - if (lookupTable.TryGetValue(type, out string assemblyName)) + return lookupTable.TryGetValue(referencedType, out assemblyReferenceName); + } + + static string GetNameOfAssemblyRefWhichResolvesToType(ModuleDesc module, MetadataType type) + { + if (TryGetAssemblyReferenceNameForTypeReference(module, type, out string assemblyName)) { return assemblyName; } @@ -149,6 +159,48 @@ static string GetNameOfAssemblyRefWhichResolvesToType(ModuleDesc module, Metadat } } + internal static bool CanCreateReferenceToType(ModuleDesc sourceModule, MetadataType type, ReadyToRunCompilationModuleGroupBase compilationGroup) + { + ModuleDesc targetModule = type.Module; + if (targetModule == type.Context.SystemModule || + compilationGroup.CrossModuleInlineableModule(targetModule) || + compilationGroup.VersionsWithModule(targetModule)) + { + return true; + } + + if (sourceModule is not EcmaModule sourceEcmaModule || targetModule is not EcmaModule targetEcmaModule) + { + return false; + } + + // An existing TypeRef in the source module (potentially resolving to `type` through a + // type-forwarder) already proves a matching AssemblyRef exists. + if (ManagedBinaryEmitterForInternalUse.TryGetAssemblyReferenceNameForTypeReference(sourceEcmaModule, type, out _)) + { + return true; + } + + // Otherwise falls back to the type's defining assembly name, ensuring the + // source module has an AssemblyRef to it. + string targetAssemblyName = targetEcmaModule.Assembly.GetName().Name; + return HasAssemblyReference(sourceEcmaModule, targetAssemblyName); + } + + private static bool HasAssemblyReference(EcmaModule module, string assemblyName) + { + foreach (AssemblyReferenceHandle assemblyReferenceHandle in module.MetadataReader.AssemblyReferences) + { + AssemblyReference assemblyReference = module.MetadataReader.GetAssemblyReference(assemblyReferenceHandle); + if (module.MetadataReader.StringComparer.Equals(assemblyReference.Name, assemblyName)) + { + return true; + } + } + + return false; + } + class Cache { private List> _values = new List>();