Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -71,7 +76,7 @@ public override IEnumerable<DependencyListEntry> 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;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -71,7 +76,7 @@ public override IEnumerable<DependencyListEntry> 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;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<TypeDesc> _externalTypeMapsRequiringRuntimeProcessing = [];
private readonly HashSet<TypeDesc> _proxyTypeMapsRequiringRuntimeProcessing = [];

public override ModuleDesc AssociatedModule => triggeringModule;

public override void AttachToDependencyGraph(DependencyAnalyzerBase<NodeFactory> graph)
{
if (IsEmpty)
return;

base.AttachToDependencyGraph(graph);
foreach (var map in GetExternalTypeMaps())
{
Expand All @@ -39,15 +47,25 @@ internal override IEnumerable<IExternalTypeMapNode> 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));
}
}

internal override IEnumerable<IProxyTypeMapNode> 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));
}
}

Expand All @@ -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<TypeDesc, TypeDesc> 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;
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -124,15 +124,25 @@ static Dictionary<MetadataType, string> 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;
}
Expand All @@ -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<ValueTuple<int, object>> _values = new List<ValueTuple<int, object>>();
Expand Down
Loading