From 3cda270ed7a4f1c843b959f3175d8a7d4dde3e68 Mon Sep 17 00:00:00 2001 From: Jackson Schuster <36744439+jtschuster@users.noreply.github.com> Date: Tue, 1 Sep 2026 12:06:18 -0700 Subject: [PATCH 1/2] Support serialized type names in R2R type maps Preserve serialized type names from custom attribute blobs and encode entries that cannot use R2R fixups as lazily resolved name pairs. Bump the R2R format version to 28 and update the VM reader and tests. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- docs/design/coreclr/botr/readytorun-format.md | 79 ++++++ src/coreclr/inc/readytorun.h | 3 +- .../nativeaot/Runtime/inc/ModuleHeaders.h | 2 +- .../tools/Common/Compiler/TypeMapMetadata.cs | 192 +++++++++++-- .../Common/Internal/Runtime/ModuleHeaders.cs | 2 +- .../DependencyAnalysis/ExternalTypeMapNode.cs | 13 +- .../DependencyAnalysis/ProxyTypeMapNode.cs | 14 +- .../Compiler/ReadyToRunExternalTypeMapNode.cs | 43 ++- .../Compiler/ReadyToRunProxyTypeMapNode.cs | 47 +++- .../Compiler/ReadyToRunTypeMapEncoding.cs | 77 +++++ .../ILCompiler.ReadyToRun.csproj | 1 + .../TypeSystem/Mutable/MutableModule.cs | 63 ++++- src/coreclr/tools/aot/crossgen2.slnx | 2 +- src/coreclr/vm/assemblynative.cpp | 67 ++++- src/coreclr/vm/nativeformatreader.h | 7 + src/coreclr/vm/readytoruninfo.cpp | 265 +++++++++--------- src/coreclr/vm/readytoruninfo.h | 2 + src/tests/Interop/TypeMap/TypeMapApp.cs | 6 + .../Interop/TypeMap/TypeMapBlobOnlyLib.il | 10 + 19 files changed, 687 insertions(+), 208 deletions(-) create mode 100644 src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunTypeMapEncoding.cs diff --git a/docs/design/coreclr/botr/readytorun-format.md b/docs/design/coreclr/botr/readytorun-format.md index fb944655f044c3..968a33722a0bde 100644 --- a/docs/design/coreclr/botr/readytorun-format.md +++ b/docs/design/coreclr/botr/readytorun-format.md @@ -187,6 +187,9 @@ The following section types are defined and described later in this document: | MethodIsGenericMap | 121 | Assembly (Added in V9.0) | EnclosingTypeMap | 122 | Assembly (Added in V9.0) | TypeGenericInfoMap | 123 | Assembly (Added in V9.0) +| ExternalTypeMaps | 124 | Assembly (added in V18.3, extended in V28) +| ProxyTypeMaps | 125 | Assembly (added in V18.3, extended in V28) +| TypeMapAssemblyTargets | 126 | Assembly (added in V18.3) ## ReadyToRunSectionType.CompilerIdentifier @@ -762,6 +765,82 @@ TypeGenericInfoMap entries have 4 bits representing 3 different sets of informat 2. Are there any constraints on the generic parameters? (This is the 3rd bit of the entry) 3. Do any of the generic parameters have co or contra variance? (This is the 4th bit of the entry) +## ReadyToRunSectionType.ExternalTypeMaps (v18.3+) + +This optional section contains precomputed external type maps. It is a native hashtable keyed by the version-resilient hash code of the type map group. Each value has the following layout: + +```text +GroupTypeFixup +State +[ExternalTypeMapHashtable] +[NamedEntries] +``` + +`GroupTypeFixup` is a fixup reference encoded as the import section index followed by the fixup row index. `State` is a compressed unsigned integer with one of these values: + +| State | Name | Remaining layout | +|------:|:-----|:-----------------| +| 0 | Runtime attribute fallback | No precomputed map data. The runtime processes the type map attributes instead. | +| 1 | Precomputed fixups | `ExternalTypeMapHashtable` only. | +| 2 | Precomputed fixups and type names | `ExternalTypeMapHashtable` followed immediately by `NamedEntries`. Added in V27.1. | + +`ExternalTypeMapHashtable` is a native hashtable keyed by the name hash code of the external type map key. Each value contains: + +```text +KeyString +TargetTypeFixup +``` + +`TargetTypeFixup` has the same import-section-index and fixup-row-index encoding as `GroupTypeFixup`. + +In state 2, `NamedEntries` is a native sequence: a compressed unsigned count followed by that many inline pairs. Each pair contains: + +```text +KeyString +SerializedTargetTypeName +``` + +These entries represent target types that cannot be encoded as ReadyToRun fixups. The endpoint stored in the final bucket-offset cell of `ExternalTypeMapHashtable` identifies the start of `NamedEntries`; a hashtable lookup therefore does not enumerate the string sequence. + +## ReadyToRunSectionType.ProxyTypeMaps (v18.3+) + +This optional section contains precomputed proxy type maps. Its outer native hashtable and per-group `GroupTypeFixup` and `State` fields use the same layout as `ExternalTypeMaps`: + +```text +GroupTypeFixup +State +[ProxyTypeMapHashtable] +[NamedEntries] +``` + +`ProxyTypeMapHashtable` is keyed by the version-resilient hash code of the source type. Each value contains: + +```text +SourceTypeFixup +ProxyTypeFixup +``` + +In state 2, `NamedEntries` begins immediately after `ProxyTypeMapHashtable` and contains a compressed unsigned count followed by that many inline pairs: + +```text +SerializedSourceTypeName +SerializedProxyTypeName +``` + +If either type in a proxy mapping cannot be encoded as a ReadyToRun fixup, both types are represented by their serialized names in `NamedEntries`. + +## ReadyToRunSectionType.TypeMapAssemblyTargets (v18.3+) + +This optional section is a native hashtable keyed by the version-resilient hash code of the type map group. Each value contains the group type fixup followed by a native sequence of module fixups: + +```text +GroupTypeFixup +TargetModuleCount +TargetModuleFixup[TargetModuleCount] +``` + +Each type or module fixup is encoded as an import section index followed by a fixup row index. + # Native Format Native format is set of encoding patterns that allow persisting type system data in a binary format that is diff --git a/src/coreclr/inc/readytorun.h b/src/coreclr/inc/readytorun.h index c81c19a6017005..2135a1eda9eada 100644 --- a/src/coreclr/inc/readytorun.h +++ b/src/coreclr/inc/readytorun.h @@ -19,7 +19,7 @@ // src/coreclr/nativeaot/Runtime/inc/ModuleHeaders.h // If you update this, ensure you run `git grep MINIMUM_READYTORUN_MAJOR_VERSION` // and handle pending work. -#define READYTORUN_MAJOR_VERSION 27 +#define READYTORUN_MAJOR_VERSION 28 #define READYTORUN_MINOR_VERSION 0x0000 #define MINIMUM_READYTORUN_MAJOR_VERSION 26 @@ -68,6 +68,7 @@ // R2R Version 26 changes ARM64 NativeVarInfo register encoding to include V0-V31 // R2R Version 26.1 adds READYTORUN_FIXUP_StoreMultiCallableAddrOfCode for storing a method's MultiCallableAddrOfCode into a location in the R2R image (used on WebAssembly) // R2R Version 27 redefines READYTORUN_FIXUP_DeclaringTypeHandle to be encoded as a method signature instead of a pair of type signatures +// R2R Version 28 allows entries in the ExternalTypeMaps and ProxyTypeMaps sections to append a sequence of serialized (string, string) type map entries after the per-group NativeHashtable. struct READYTORUN_CORE_HEADER { diff --git a/src/coreclr/nativeaot/Runtime/inc/ModuleHeaders.h b/src/coreclr/nativeaot/Runtime/inc/ModuleHeaders.h index 33f47c9cb339a1..a60454d7c02416 100644 --- a/src/coreclr/nativeaot/Runtime/inc/ModuleHeaders.h +++ b/src/coreclr/nativeaot/Runtime/inc/ModuleHeaders.h @@ -11,7 +11,7 @@ struct ReadyToRunHeaderConstants { static const uint32_t Signature = 0x00525452; // 'RTR' - static const uint32_t CurrentMajorVersion = 27; + static const uint32_t CurrentMajorVersion = 28; static const uint32_t CurrentMinorVersion = 0; }; diff --git a/src/coreclr/tools/Common/Compiler/TypeMapMetadata.cs b/src/coreclr/tools/Common/Compiler/TypeMapMetadata.cs index d929eab6a773f4..541445ea041bda 100644 --- a/src/coreclr/tools/Common/Compiler/TypeMapMetadata.cs +++ b/src/coreclr/tools/Common/Compiler/TypeMapMetadata.cs @@ -24,6 +24,94 @@ private enum TypeMapAttributeKind TypeMapAssociation } + private readonly struct TypeWithSerializedName + { + public TypeWithSerializedName(TypeDesc type, string serializedName) + { + Type = type; + SerializedName = serializedName; + } + + public TypeDesc Type { get; } + public string SerializedName { get; } + } + + private readonly struct TypeMapCustomAttributeTypeProvider : ICustomAttributeTypeProvider + { + private readonly CustomAttributeTypeProvider _provider; + + public TypeMapCustomAttributeTypeProvider(EcmaModule module) + { + _provider = new CustomAttributeTypeProvider(module); + } + + public TypeWithSerializedName GetPrimitiveType(PrimitiveTypeCode typeCode) + => new TypeWithSerializedName(_provider.GetPrimitiveType(typeCode), null); + + public TypeWithSerializedName GetSystemType() + => new TypeWithSerializedName(_provider.GetSystemType(), null); + + public TypeWithSerializedName GetSZArrayType(TypeWithSerializedName elementType) + => new TypeWithSerializedName(_provider.GetSZArrayType(elementType.Type), null); + + public TypeWithSerializedName GetTypeFromDefinition(MetadataReader reader, TypeDefinitionHandle handle, byte rawTypeKind) + => new TypeWithSerializedName(_provider.GetTypeFromDefinition(reader, handle, rawTypeKind), null); + + public TypeWithSerializedName GetTypeFromReference(MetadataReader reader, TypeReferenceHandle handle, byte rawTypeKind) + => new TypeWithSerializedName(_provider.GetTypeFromReference(reader, handle, rawTypeKind), null); + + public TypeWithSerializedName GetTypeFromSpecification(MetadataReader reader, TypeSpecificationHandle handle, byte rawTypeKind) + => new TypeWithSerializedName(_provider.GetTypeFromSpecification(reader, handle, rawTypeKind), null); + + public TypeWithSerializedName GetTypeFromSerializedName(string name) + => new TypeWithSerializedName(_provider.GetTypeFromSerializedName(name), name); + + public PrimitiveTypeCode GetUnderlyingEnumType(TypeWithSerializedName type) + => _provider.GetUnderlyingEnumType(type.Type); + + public bool IsSystemType(TypeWithSerializedName type) + => _provider.IsSystemType(type.Type); + } + + internal readonly struct ExternalTypeMapEntry + { + public ExternalTypeMapEntry( + TypeDesc type, + string serializedTypeName, + TypeDesc trimmingType, + string serializedTrimmingTypeName, + ModuleDesc declaringModule) + { + Type = type; + SerializedTypeName = serializedTypeName; + TrimmingType = trimmingType; + SerializedTrimmingTypeName = serializedTrimmingTypeName; + DeclaringModule = declaringModule; + } + + public TypeDesc Type { get; } + public string SerializedTypeName { get; } + public TypeDesc TrimmingType { get; } + public string SerializedTrimmingTypeName { get; } + public ModuleDesc DeclaringModule { get; } + } + + internal readonly struct ProxyTypeMapEntry + { + public ProxyTypeMapEntry(TypeDesc type, string serializedSourceTypeName, string serializedTypeName, ModuleDesc declaringModule) + { + Type = type; + SerializedSourceTypeName = serializedSourceTypeName; + SerializedTypeName = serializedTypeName; + DeclaringModule = declaringModule; + } + + public TypeDesc Type { get; } + public string SerializedSourceTypeName { get; } + public string SerializedTypeName { get; } + public ModuleDesc DeclaringModule { get; } + } + private static TypeMapAttributeKind LookupTypeMapType(TypeDesc attrType) { var typeDef = attrType.GetTypeDefinition() as MetadataType; @@ -41,13 +129,13 @@ private static TypeMapAttributeKind LookupTypeMapType(TypeDesc attrType) internal interface IExternalTypeMap { - IReadOnlyDictionary TypeMap { get; } + IReadOnlyDictionary TypeMap { get; } MethodDesc ThrowingMethodStub { get; } } internal interface IProxyTypeMap { - IReadOnlyDictionary TypeMap { get; } + IReadOnlyDictionary TypeMap { get; } MethodDesc ThrowingMethodStub { get; } } @@ -95,8 +183,8 @@ protected override int CompareToImpl(MethodDesc other, TypeSystemComparer compar public override TypeSystemContext Context => OwningType.Context; } - private readonly Dictionary _associatedTypeMap = []; - private readonly Dictionary _externalTypeMap = []; + private readonly Dictionary _associatedTypeMap = []; + private readonly Dictionary _externalTypeMap = []; private readonly List _targetModules = []; private ThrowingMethodStub _externalTypeMapExceptionStub; private ThrowingMethodStub _associatedTypeMapExceptionStub; @@ -116,16 +204,31 @@ public Map(TypeDesc typeMapGroup) /// public bool HasAssemblyTargetAttributes { get; set; } - public void AddAssociatedTypeMapEntry(TypeDesc type, TypeDesc associatedType) + public void AddAssociatedTypeMapEntry( + TypeDesc type, + string serializedTypeName, + TypeDesc associatedType, + string serializedAssociatedTypeName, + ModuleDesc declaringModule) { - if (!_associatedTypeMap.TryAdd(type, associatedType)) + if (!_associatedTypeMap.TryAdd( + type, + new ProxyTypeMapEntry(associatedType, serializedTypeName, serializedAssociatedTypeName, declaringModule))) { ThrowHelper.ThrowBadImageFormatException(); } } - public void AddExternalTypeMapEntry(string typeName, TypeDesc type, TypeDesc trimmingTarget) + public void AddExternalTypeMapEntry( + string typeName, + TypeDesc type, + string serializedTypeName, + TypeDesc trimmingTarget, + string serializedTrimmingTypeName, + ModuleDesc declaringModule) { - if (!_externalTypeMap.TryAdd(typeName, (type, trimmingTarget))) + if (!_externalTypeMap.TryAdd( + typeName, + new ExternalTypeMapEntry(type, serializedTypeName, trimmingTarget, serializedTrimmingTypeName, declaringModule))) { ThrowHelper.ThrowBadImageFormatException(); } @@ -165,9 +268,14 @@ public void MergePendingMap(ModuleDesc stubModule, Map pendingMap) { try { - foreach (KeyValuePair kvp in pendingMap._associatedTypeMap) + foreach (KeyValuePair kvp in pendingMap._associatedTypeMap) { - AddAssociatedTypeMapEntry(kvp.Key, kvp.Value); + AddAssociatedTypeMapEntry( + kvp.Key, + kvp.Value.SerializedSourceTypeName, + kvp.Value.Type, + kvp.Value.SerializedTypeName, + kvp.Value.DeclaringModule); } } catch (TypeSystemException ex) @@ -195,9 +303,15 @@ public void MergePendingMap(ModuleDesc stubModule, Map pendingMap) { try { - foreach (KeyValuePair kvp in pendingMap._externalTypeMap) + foreach (KeyValuePair kvp in pendingMap._externalTypeMap) { - AddExternalTypeMapEntry(kvp.Key, kvp.Value.type, kvp.Value.trimmingTarget); + AddExternalTypeMapEntry( + kvp.Key, + kvp.Value.Type, + kvp.Value.SerializedTypeName, + kvp.Value.TrimmingType, + kvp.Value.SerializedTrimmingTypeName, + kvp.Value.DeclaringModule); } } catch (TypeSystemException ex) @@ -227,11 +341,11 @@ public void AddTargetModule(ModuleDesc targetModule) /// public IReadOnlyList TargetModules => _targetModules; - IReadOnlyDictionary IExternalTypeMap.TypeMap => _externalTypeMap; + IReadOnlyDictionary IExternalTypeMap.TypeMap => _externalTypeMap; MethodDesc IExternalTypeMap.ThrowingMethodStub => _externalTypeMapExceptionStub; - IReadOnlyDictionary IProxyTypeMap.TypeMap => _associatedTypeMap; + IReadOnlyDictionary IProxyTypeMap.TypeMap => _associatedTypeMap; MethodDesc IProxyTypeMap.ThrowingMethodStub => _associatedTypeMapExceptionStub; } @@ -328,7 +442,7 @@ public static TypeMapMetadata CreateFromAssembly(EcmaAssembly assembly, ModuleDe continue; } - CustomAttributeValue attrValue = attr.DecodeValue(new CustomAttributeTypeProvider(currentAssembly)); + CustomAttributeValue attrValue = attr.DecodeValue(new TypeMapCustomAttributeTypeProvider(currentAssembly)); TypeDesc typeMapGroup = type.Instantiation[0]; @@ -420,7 +534,7 @@ public static TypeMapMetadata CreateFromAssembly(EcmaAssembly assembly, ModuleDe scannedAssemblies.Add((currentAssembly, currentTypeMapGroup)); } - void ProcessTypeMapAssemblyTargetAttribute(CustomAttributeValue attrValue, Map typeMapState) + void ProcessTypeMapAssemblyTargetAttribute(CustomAttributeValue attrValue, Map typeMapState) { typeMapState.HasAssemblyTargetAttributes = true; @@ -442,19 +556,40 @@ void ProcessTypeMapAssemblyTargetAttribute(CustomAttributeValue attrVa } } - void ProcessTypeMapAttribute(CustomAttributeValue attrValue, Map typeMapState) + void ProcessTypeMapAttribute(CustomAttributeValue attrValue, Map typeMapState) { switch (attrValue.FixedArguments) { - case [{ Value: string typeName }, { Value: TypeDesc targetType }]: + case + [ + { Value: string typeName }, + { Value: TypeWithSerializedName { Type: TypeDesc targetType, SerializedName: string serializedTargetTypeName } } + ]: { - typeMapState.AddExternalTypeMapEntry(typeName, targetType, null); + typeMapState.AddExternalTypeMapEntry( + typeName, + targetType, + serializedTargetTypeName, + null, + null, + currentAssembly); break; } - case [{ Value: string typeName }, { Value: TypeDesc targetType }, { Value: TypeDesc trimTargetType }]: + case + [ + { Value: string typeName }, + { Value: TypeWithSerializedName { Type: TypeDesc targetType, SerializedName: string serializedTargetTypeName } }, + { Value: TypeWithSerializedName { Type: TypeDesc trimTargetType, SerializedName: string serializedTrimTargetTypeName } } + ]: { - typeMapState.AddExternalTypeMapEntry(typeName, targetType, trimTargetType); + typeMapState.AddExternalTypeMapEntry( + typeName, + targetType, + serializedTargetTypeName, + trimTargetType, + serializedTrimTargetTypeName, + currentAssembly); break; } @@ -464,17 +599,26 @@ void ProcessTypeMapAttribute(CustomAttributeValue attrValue, Map typeM } } - void ProcessTypeMapAssociationAttribute(CustomAttributeValue attrValue, Map typeMapState) + void ProcessTypeMapAssociationAttribute(CustomAttributeValue attrValue, Map typeMapState) { // If attribute is TypeMapAssociationAttribute, we need to extract the generic argument (type map group) // and process it. - if (attrValue.FixedArguments is not [{ Value: TypeDesc type }, { Value: TypeDesc associatedType }]) + if (attrValue.FixedArguments is not + [ + { Value: TypeWithSerializedName { Type: TypeDesc type, SerializedName: string serializedTypeName } }, + { Value: TypeWithSerializedName { Type: TypeDesc associatedType, SerializedName: string serializedAssociatedTypeName } } + ]) { ThrowHelper.ThrowBadImageFormatException(); return; } - typeMapState.AddAssociatedTypeMapEntry(type, associatedType); + typeMapState.AddAssociatedTypeMapEntry( + type, + serializedTypeName, + associatedType, + serializedAssociatedTypeName, + currentAssembly); } } diff --git a/src/coreclr/tools/Common/Internal/Runtime/ModuleHeaders.cs b/src/coreclr/tools/Common/Internal/Runtime/ModuleHeaders.cs index 2599ae355ebf5d..733e4e3f4c6996 100644 --- a/src/coreclr/tools/Common/Internal/Runtime/ModuleHeaders.cs +++ b/src/coreclr/tools/Common/Internal/Runtime/ModuleHeaders.cs @@ -15,7 +15,7 @@ internal struct ReadyToRunHeaderConstants { public const uint Signature = 0x00525452; // 'RTR' - public const ushort CurrentMajorVersion = 27; + public const ushort CurrentMajorVersion = 28; public const ushort CurrentMinorVersion = 0; } #if READYTORUN diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ExternalTypeMapNode.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ExternalTypeMapNode.cs index 3f2e82302b3bdd..70ed4b62fb8598 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ExternalTypeMapNode.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ExternalTypeMapNode.cs @@ -13,9 +13,9 @@ namespace ILCompiler.DependencyAnalysis { internal sealed class ExternalTypeMapNode : SortableDependencyNode, IExternalTypeMapNode { - private readonly IEnumerable> _mapEntries; + private readonly IEnumerable> _mapEntries; - public ExternalTypeMapNode(TypeDesc typeMapGroup, IEnumerable> mapEntries) + public ExternalTypeMapNode(TypeDesc typeMapGroup, IEnumerable> mapEntries) { _mapEntries = mapEntries; TypeMapGroup = typeMapGroup; @@ -37,7 +37,8 @@ public override IEnumerable GetConditionalStaticDep foreach (var entry in _mapEntries) { - var (targetType, trimmingTargetType) = entry.Value; + TypeDesc targetType = entry.Value.Type; + TypeDesc trimmingTargetType = entry.Value.TrimmingType; if (trimmingTargetType is not null) { IEETypeNode effectiveTrimTargetType = GetEffectiveTrimTargetType(context, trimmingTargetType); @@ -58,7 +59,8 @@ public override IEnumerable GetStaticDependencies(NodeFacto { foreach (var entry in _mapEntries) { - var (targetType, trimmingTargetType) = entry.Value; + TypeDesc targetType = entry.Value.Type; + TypeDesc trimmingTargetType = entry.Value.TrimmingType; if (trimmingTargetType is null) { yield return new DependencyListEntry( @@ -83,7 +85,8 @@ public override int CompareToImpl(ISortableNode other, CompilerComparer comparer { foreach (var entry in _mapEntries) { - var (targetType, trimmingTargetType) = entry.Value; + TypeDesc targetType = entry.Value.Type; + TypeDesc trimmingTargetType = entry.Value.TrimmingType; if (trimmingTargetType is null || GetEffectiveTrimTargetType(factory, trimmingTargetType).Marked) diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ProxyTypeMapNode.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ProxyTypeMapNode.cs index e82bb502c2cbe6..46059fb6484fe4 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ProxyTypeMapNode.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ProxyTypeMapNode.cs @@ -15,9 +15,9 @@ namespace ILCompiler.DependencyAnalysis { internal sealed class ProxyTypeMapNode : SortableDependencyNode, IProxyTypeMapNode { - private readonly IEnumerable> _mapEntries; + private readonly IEnumerable> _mapEntries; - public ProxyTypeMapNode(TypeDesc typeMapGroup, IEnumerable> mapEntries) + public ProxyTypeMapNode(TypeDesc typeMapGroup, IEnumerable> mapEntries) { _mapEntries = mapEntries; TypeMapGroup = typeMapGroup; @@ -25,7 +25,7 @@ public ProxyTypeMapNode(TypeDesc typeMapGroup, IEnumerable> MapEntries => _mapEntries; + public IEnumerable> MapEntries => _mapEntries; public override bool InterestingForDynamicDependencyAnalysis => false; public override bool HasDynamicDependencies => false; @@ -40,10 +40,10 @@ public ProxyTypeMapNode(TypeDesc typeMapGroup, IEnumerable GetConditionalStaticDependencies(NodeFactory context) { - foreach (var (key, value) in _mapEntries) + foreach (var (key, entry) in _mapEntries) { yield return new CombinedDependencyListEntry( - context.MetadataTypeSymbol(value), + context.MetadataTypeSymbol(entry.Type), context.MaximallyConstructableType(key), "Proxy type map entry"); @@ -66,12 +66,12 @@ public override IEnumerable GetConditionalStaticDep private IEnumerable<(IEETypeNode key, IEETypeNode value)> GetMarkedEntries(NodeFactory factory) { - foreach (var (key, value) in MapEntries) + foreach (var (key, entry) in MapEntries) { IEETypeNode keyNode = factory.MaximallyConstructableType(key); if (keyNode.Marked) { - IEETypeNode valueNode = factory.MetadataTypeSymbol(value); + IEETypeNode valueNode = factory.MetadataTypeSymbol(entry.Type); Debug.Assert(valueNode.Marked); yield return (keyNode, valueNode); } diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunExternalTypeMapNode.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunExternalTypeMapNode.cs index d02586c9093220..b33b3e6ad149a7 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunExternalTypeMapNode.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunExternalTypeMapNode.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Collections.Generic; +using System.Diagnostics; using System.Text; using ILCompiler.DependencyAnalysis; using ILCompiler.DependencyAnalysisFramework; @@ -46,23 +47,44 @@ public Vertex CreateTypeMap(NodeFactory factory, NativeWriter writer, Section se // as emitting loose methods is not supported/very expensive. // Instead, we defer to the runtime to generate the type map // and throw on error cases. - return section.Place(writer.GetTuple(typeMapGroupVertex, writer.GetUnsignedConstant(0))); // Invalid type map state + return section.Place(writer.GetTuple( + typeMapGroupVertex, + writer.GetUnsignedConstant(ReadyToRunTypeMapEncoding.RuntimeAttributeFallback))); } VertexHashtable typeMapHashTable = new(); + VertexSequence namedEntries = new(); + bool hasNamedEntries = false; Section typeMapEntriesSection = writer.NewSection(); - foreach ((string key, (TypeDesc type, _)) in map.TypeMap) + foreach ((string key, TypeMapMetadata.ExternalTypeMapEntry mapEntry) in map.TypeMap) { - Vertex keyVertex = writer.GetStringConstant(key); - Vertex valueVertex = externalReferences.EncodeReferenceToType(writer, type, TriggeringModule); - Vertex entry = writer.GetTuple(keyVertex, valueVertex); - typeMapHashTable.Append((uint)VersionResilientHashCode.NameHashCode(Encoding.UTF8.GetBytes(key)), typeMapEntriesSection.Place(entry)); + if (ReadyToRunTypeMapEncoding.IsTypeDescEncodable(factory, TriggeringModule, mapEntry.Type)) + { + Vertex entry = writer.GetTuple( + writer.GetStringConstant(key), + externalReferences.EncodeReferenceToType(writer, mapEntry.Type, TriggeringModule)); + typeMapHashTable.Append((uint)VersionResilientHashCode.NameHashCode(Encoding.UTF8.GetBytes(key)), typeMapEntriesSection.Place(entry)); + } + else + { + Debug.Assert(TriggeringModule.Assembly == mapEntry.DeclaringModule.Assembly); + namedEntries.Append(writer.GetTuple( + writer.GetStringConstant(key), + writer.GetStringConstant(mapEntry.SerializedTypeName))); + hasNamedEntries = true; + } } - Vertex typeMapStateVertex = writer.GetUnsignedConstant(1); // Valid type map state - Vertex tuple = writer.GetTuple(typeMapGroupVertex, typeMapStateVertex, typeMapHashTable); + uint typeMapState = hasNamedEntries + ? ReadyToRunTypeMapEncoding.PrecomputedFixupsAndTypeNames + : ReadyToRunTypeMapEncoding.PrecomputedFixups; + Vertex typeMapStateVertex = writer.GetUnsignedConstant(typeMapState); + Vertex typeMapData = hasNamedEntries + ? writer.GetTuple(typeMapHashTable, namedEntries) + : typeMapHashTable; + Vertex tuple = writer.GetTuple(typeMapGroupVertex, typeMapStateVertex, typeMapData); return section.Place(tuple); } @@ -78,7 +100,10 @@ public override IEnumerable GetStaticDependencies(NodeFacto foreach (var entry in map.TypeMap) { - yield return new DependencyListEntry(importProvider.GetImportToType(entry.Value.type, TriggeringModule), $"External type map entry target for key '{entry.Key}'"); + if (ReadyToRunTypeMapEncoding.IsTypeDescEncodable(context, TriggeringModule, entry.Value.Type)) + { + yield return new DependencyListEntry(importProvider.GetImportToType(entry.Value.Type, TriggeringModule), $"External type map entry target for key '{entry.Key}'"); + } } } public override IEnumerable SearchDynamicDependencies(List> markedNodes, int firstNode, NodeFactory context) => []; diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunProxyTypeMapNode.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunProxyTypeMapNode.cs index 6f5e80a00f2e4b..26b22e996e56f6 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunProxyTypeMapNode.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunProxyTypeMapNode.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Collections.Generic; +using System.Diagnostics; using ILCompiler.DependencyAnalysis; using ILCompiler.DependencyAnalysisFramework; using Internal.NativeFormat; @@ -46,23 +47,45 @@ public Vertex CreateTypeMap(NodeFactory factory, NativeWriter writer, Section se // in the managed type system. // Instead, we defer to the runtime to generate the type map // and throw on error cases. - return section.Place(writer.GetTuple(typeMapGroupVertex, writer.GetUnsignedConstant(0))); // Invalid type map state + return section.Place(writer.GetTuple( + typeMapGroupVertex, + writer.GetUnsignedConstant(ReadyToRunTypeMapEncoding.RuntimeAttributeFallback))); } VertexHashtable typeMapHashTable = new(); + VertexSequence namedEntries = new(); + bool hasNamedEntries = false; Section typeMapEntriesSection = writer.NewSection(); - foreach ((TypeDesc type, TypeDesc targetType) in map.TypeMap) + foreach ((TypeDesc type, TypeMapMetadata.ProxyTypeMapEntry mapEntry) in map.TypeMap) { - Vertex keyVertex = ProxyReferences.EncodeReferenceToType(writer, type, TriggeringModule); - Vertex valueVertex = ProxyReferences.EncodeReferenceToType(writer, targetType, TriggeringModule); - Vertex entry = writer.GetTuple(keyVertex, valueVertex); - typeMapHashTable.Append((uint)type.GetHashCode(), typeMapEntriesSection.Place(entry)); + if (ReadyToRunTypeMapEncoding.IsTypeDescEncodable(factory, TriggeringModule, type) && + ReadyToRunTypeMapEncoding.IsTypeDescEncodable(factory, TriggeringModule, mapEntry.Type)) + { + Vertex entry = writer.GetTuple( + ProxyReferences.EncodeReferenceToType(writer, type, TriggeringModule), + ProxyReferences.EncodeReferenceToType(writer, mapEntry.Type, TriggeringModule)); + typeMapHashTable.Append((uint)type.GetHashCode(), typeMapEntriesSection.Place(entry)); + } + else + { + Debug.Assert(TriggeringModule.Assembly == mapEntry.DeclaringModule.Assembly); + namedEntries.Append(writer.GetTuple( + writer.GetStringConstant(mapEntry.SerializedSourceTypeName), + writer.GetStringConstant(mapEntry.SerializedTypeName))); + hasNamedEntries = true; + } } - Vertex typeMapStateVertex = writer.GetUnsignedConstant(1); // Valid type map state - Vertex tuple = writer.GetTuple(typeMapGroupVertex, typeMapStateVertex, typeMapHashTable); + uint typeMapState = hasNamedEntries + ? ReadyToRunTypeMapEncoding.PrecomputedFixupsAndTypeNames + : ReadyToRunTypeMapEncoding.PrecomputedFixups; + Vertex typeMapStateVertex = writer.GetUnsignedConstant(typeMapState); + Vertex typeMapData = hasNamedEntries + ? writer.GetTuple(typeMapHashTable, namedEntries) + : typeMapHashTable; + Vertex tuple = writer.GetTuple(typeMapGroupVertex, typeMapStateVertex, typeMapData); return section.Place(tuple); } @@ -78,8 +101,12 @@ public override IEnumerable GetStaticDependencies(NodeFacto foreach (var entry in map.TypeMap) { - yield return new DependencyListEntry(importProvider.GetImportToType(entry.Key, TriggeringModule), $"Key type of Proxy type map entry"); - yield return new DependencyListEntry(importProvider.GetImportToType(entry.Value, TriggeringModule), $"Proxy type map entry target for key '{entry.Key}'"); + if (ReadyToRunTypeMapEncoding.IsTypeDescEncodable(context, TriggeringModule, entry.Key) && + ReadyToRunTypeMapEncoding.IsTypeDescEncodable(context, TriggeringModule, entry.Value.Type)) + { + yield return new DependencyListEntry(importProvider.GetImportToType(entry.Key, TriggeringModule), $"Key type of Proxy type map entry"); + yield return new DependencyListEntry(importProvider.GetImportToType(entry.Value.Type, TriggeringModule), $"Proxy type map entry target for key '{entry.Key}'"); + } } } public override IEnumerable SearchDynamicDependencies(List> markedNodes, int firstNode, NodeFactory context) => []; diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunTypeMapEncoding.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunTypeMapEncoding.cs new file mode 100644 index 00000000000000..b84856b6b33cdd --- /dev/null +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunTypeMapEncoding.cs @@ -0,0 +1,77 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using ILCompiler.DependencyAnalysis; +using Internal.TypeSystem; +using Internal.TypeSystem.Ecma; + +namespace ILCompiler.ReadyToRun +{ + internal static class ReadyToRunTypeMapEncoding + { + // Keep in sync with TypeMapState in readytoruninfo.cpp. + public const uint RuntimeAttributeFallback = 0; + public const uint PrecomputedFixups = 1; + public const uint PrecomputedFixupsAndTypeNames = 2; + + public static bool IsTypeDescEncodable(NodeFactory factory, ModuleDesc sourceModule, TypeDesc type) + { + if (factory.CompilationModuleGroup.VersionsWithTypeReference(type)) + { + return true; + } + + if (type is EcmaType ecmaType) + { + return MutableModule.CanCreateReferenceToType( + sourceModule, + ecmaType, + (ReadyToRunCompilationModuleGroupBase)factory.CompilationModuleGroup); + } + + if (type.IsParameterizedType) + { + return IsTypeDescEncodable(factory, sourceModule, ((ParameterizedType)type).ParameterType); + } + + if (type.IsFunctionPointer) + { + MethodSignature signature = ((FunctionPointerType)type).Signature; + if (!IsTypeDescEncodable(factory, sourceModule, signature.ReturnType)) + { + return false; + } + + for (int i = 0; i < signature.Length; i++) + { + if (!IsTypeDescEncodable(factory, sourceModule, signature[i])) + { + return false; + } + } + + return true; + } + + if (type.HasInstantiation) + { + if (!IsTypeDescEncodable(factory, sourceModule, type.GetTypeDefinition())) + { + return false; + } + + foreach (TypeDesc instantiationArgument in type.Instantiation) + { + if (!IsTypeDescEncodable(factory, sourceModule, instantiationArgument)) + { + return false; + } + } + + return true; + } + + return type.IsSignatureVariable; + } + } +} diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ILCompiler.ReadyToRun.csproj b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ILCompiler.ReadyToRun.csproj index 2fad7885d05cc8..e856b31e05db7c 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ILCompiler.ReadyToRun.csproj +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ILCompiler.ReadyToRun.csproj @@ -238,6 +238,7 @@ + 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..f29a6c72863d9d 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/TypeSystem/Mutable/MutableModule.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/TypeSystem/Mutable/MutableModule.cs @@ -124,31 +124,78 @@ static Dictionary ComputeTypeLookupTable(ModuleDesc module return result; } - static string GetNameOfAssemblyRefWhichResolvesToType(ModuleDesc module, MetadataType type) + 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; } // Some producers encode type map custom attributes without emitting matching TypeRef rows // for the referenced types. In that case, fall back to the target type's defining assembly. - if (module is EcmaModule ecmaModule && type.Module is EcmaModule targetTypeModule) + if (module is EcmaModule && type.Module is EcmaModule targetTypeModule) { - string targetAssemblyName = targetTypeModule.Assembly.GetName().Name; - return targetAssemblyName; + return targetTypeModule.Assembly.GetName().Name; } throw new KeyNotFoundException($"Unable to resolve an assembly reference from module '{module}' to type '{type}'."); } } + 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; + } + + if (ManagedBinaryEmitterForInternalUse.TryGetAssemblyReferenceNameForTypeReference(sourceEcmaModule, type, out _)) + { + return true; + } + + return HasAssemblyReference(sourceEcmaModule, targetEcmaModule.Assembly.GetName().Name); + } + + 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>(); diff --git a/src/coreclr/tools/aot/crossgen2.slnx b/src/coreclr/tools/aot/crossgen2.slnx index b2cd7b76b2bf64..ed67a8040cdc9b 100644 --- a/src/coreclr/tools/aot/crossgen2.slnx +++ b/src/coreclr/tools/aot/crossgen2.slnx @@ -3,9 +3,9 @@ + - diff --git a/src/coreclr/vm/assemblynative.cpp b/src/coreclr/vm/assemblynative.cpp index 09dcff1745694c..cf1b2cfc01a8b8 100644 --- a/src/coreclr/vm/assemblynative.cpp +++ b/src/coreclr/vm/assemblynative.cpp @@ -1574,6 +1574,49 @@ namespace } return false; } + + bool ProcessPrecachedTypeMapNamedEntries( + NativeFormat::NativeParser namedEntries, + BOOL (*callback)(CallbackContext* context, ProcessAttributesCallbackArg* arg), + CallbackContext* context) + { + STANDARD_VM_CONTRACT; + + if (namedEntries.IsNull()) + { + return true; + } + + uint32_t count = namedEntries.GetUnsigned(); + for (uint32_t i = 0; i < count; i++) + { + PTR_CBYTE string1; + uint32_t stringLength1; + namedEntries.GetString(&string1, &stringLength1); + + PTR_CBYTE string2; + uint32_t stringLength2; + namedEntries.GetString(&string2, &stringLength2); + + if (stringLength1 > INT32_MAX || stringLength2 > INT32_MAX) + { + COMPlusThrowHR(COR_E_BADIMAGEFORMAT); + } + + ProcessAttributesCallbackArg arg; + arg.Utf8String1 = reinterpret_cast(string1); + arg.Utf8String2 = reinterpret_cast(string2); + arg.StringLen1 = static_cast(stringLength1); + arg.StringLen2 = static_cast(stringLength2); + + if (!callback(context, &arg)) + { + return false; + } + } + + return true; + } #endif class AssemblyPtrCollectionTraits : public DefaultSHashTraits @@ -1745,6 +1788,16 @@ extern "C" void QCALLTYPE TypeMapLazyDictionary_ProcessAttributes( { return FALSE; } + + NativeFormat::NativeHashtable typeMap; + NativeFormat::NativeParser namedEntries; + bool found = pR2RInfo->TryGetPrecachedExternalTypeMap(groupTypeMT, &typeMap, &namedEntries); + _ASSERTE(found); + if (!ProcessPrecachedTypeMapNamedEntries(namedEntries, newExternalTypeEntry, context)) + { + return FALSE; + } + return newPrecachedExternalTypeMap(context); }, currAssembly); @@ -1755,7 +1808,19 @@ extern "C" void QCALLTYPE TypeMapLazyDictionary_ProcessAttributes( { hasPrecachedProxy = ProcessPrecachedTypeMapInfo( [=](PTR_ReadyToRunInfo pR2RInfo) { return pR2RInfo->HasPrecachedProxyTypeMap(groupTypeMT); }, - [=](PTR_ReadyToRunInfo pR2RInfo) { return newPrecachedProxyTypeMap(context); }, + [=](PTR_ReadyToRunInfo pR2RInfo) + { + NativeFormat::NativeHashtable typeMap; + NativeFormat::NativeParser namedEntries; + bool found = pR2RInfo->TryGetPrecachedProxyTypeMap(groupTypeMT, &typeMap, &namedEntries); + _ASSERTE(found); + if (!ProcessPrecachedTypeMapNamedEntries(namedEntries, newProxyTypeEntry, context)) + { + return FALSE; + } + + return newPrecachedProxyTypeMap(context); + }, currAssembly); } diff --git a/src/coreclr/vm/nativeformatreader.h b/src/coreclr/vm/nativeformatreader.h index 1e0cb9f93874db..6e92e2f2abccdb 100644 --- a/src/coreclr/vm/nativeformatreader.h +++ b/src/coreclr/vm/nativeformatreader.h @@ -568,6 +568,13 @@ namespace NativeFormat bool IsNull() { return _pReader == NULL; } + NativeParser GetParserAfterTable() + { + uint endOffset; + GetParserForBucket(_bucketMask, &endOffset); + return NativeParser(_pReader, endOffset); + } + class AllEntriesEnumerator { PTR_NativeHashtable _table; diff --git a/src/coreclr/vm/readytoruninfo.cpp b/src/coreclr/vm/readytoruninfo.cpp index 0ddc7d72e47c5b..6c1ca101f78358 100644 --- a/src/coreclr/vm/readytoruninfo.cpp +++ b/src/coreclr/vm/readytoruninfo.cpp @@ -1711,6 +1711,13 @@ void ReadyToRunInfo::DisableCustomAttributeFilter() namespace { + enum class TypeMapState : uint32_t + { + RuntimeAttributeFallback = 0, + PrecomputedFixups = 1, + PrecomputedFixupsAndTypeNames = 2, + }; + TypeHandle GetTypeHandleForNativeFormatFixupReference(PTR_ReadyToRunInfo pR2RInfo, PTR_Module pModule, uint32_t importSection, uint32_t fixupIndex) { STANDARD_VM_CONTRACT; @@ -1741,6 +1748,60 @@ namespace return *(TypeHandle*)fixupAddress; } + bool TryGetPrecachedTypeMap( + PTR_ReadyToRunInfo pR2RInfo, + PTR_Module pModule, + NativeHashtable& typeMaps, + MethodTable* pGroupType, + NativeHashtable* pTypeMap, + NativeParser* pNamedEntries) + { + STANDARD_VM_CONTRACT; + + _ASSERTE(pGroupType != nullptr); + _ASSERTE(pTypeMap != nullptr); + _ASSERTE(pNamedEntries != nullptr); + + if (typeMaps.IsNull()) + { + return false; + } + + UINT32 hash = GetVersionResilientTypeHashCode(pGroupType); + NativeHashtable::Enumerator lookup = typeMaps.Lookup(hash); + NativeParser entryParser; + while (lookup.GetNext(entryParser)) + { + uint32_t importSection = entryParser.GetUnsigned(); + uint32_t fixupIndex = entryParser.GetUnsigned(); + TypeHandle typeHandle = GetTypeHandleForNativeFormatFixupReference(pR2RInfo, pModule, importSection, fixupIndex); + if (typeHandle != TypeHandle(pGroupType)) + { + continue; + } + + TypeMapState state = static_cast(entryParser.GetUnsigned()); + if (state == TypeMapState::RuntimeAttributeFallback) + { + return false; + } + + if (state != TypeMapState::PrecomputedFixups && + state != TypeMapState::PrecomputedFixupsAndTypeNames) + { + COMPlusThrowHR(COR_E_BADIMAGEFORMAT); + } + + *pTypeMap = NativeHashtable(entryParser); + *pNamedEntries = state == TypeMapState::PrecomputedFixupsAndTypeNames + ? entrpTypeMap->GetParserAfterTable() + : NativeParser(); + return true; + } + + return false; + } + Module* GetModuleForNativeFormatFixupReference(PTR_ReadyToRunInfo pR2RInfo, PTR_Module pModule, uint32_t importSection, uint32_t fixupIndex) { STANDARD_VM_CONTRACT; @@ -1772,32 +1833,19 @@ namespace } } -bool ReadyToRunInfo::HasPrecachedExternalTypeMap(MethodTable* pGroupTypeMT) +bool ReadyToRunInfo::TryGetPrecachedExternalTypeMap(MethodTable* pGroupType, NativeHashtable* pTypeMap, NativeParser* pNamedEntries) { STANDARD_VM_CONTRACT; + return TryGetPrecachedTypeMap(this, m_pModule, m_externalTypeMaps, pGroupType, pTypeMap, pNamedEntries); +} - _ASSERTE(pGroupTypeMT != nullptr); - - if (m_externalTypeMaps.IsNull()) - { - return false; - } +bool ReadyToRunInfo::HasPrecachedExternalTypeMap(MethodTable* pGroupTypeMT) +{ + STANDARD_VM_CONTRACT; - UINT32 hash = GetVersionResilientTypeHashCode(pGroupTypeMT); - NativeHashtable::Enumerator lookup = m_externalTypeMaps.Lookup(hash); - NativeParser entryParser; - while (lookup.GetNext(entryParser)) - { - uint32_t importSection = entryParser.GetUnsigned(); - uint32_t fixupIndex = entryParser.GetUnsigned(); - TypeHandle typeHandle = GetTypeHandleForNativeFormatFixupReference(this, m_pModule, importSection, fixupIndex); - if (typeHandle == TypeHandle(pGroupTypeMT)) - { - // A non-zero value next in the entry indicates that the table is valid. - return entryParser.GetUnsigned() != 0; - } - } - return false; + NativeHashtable typeMap; + NativeParser namedEntries; + return TryGetPrecachedExternalTypeMap(pGroupTypeMT, &typeMap, &namedEntries); } TypeHandle ReadyToRunInfo::FindPrecachedExternalTypeMapEntry(MethodTable* pGroupType, LPCUTF8 pKey) @@ -1805,52 +1853,28 @@ TypeHandle ReadyToRunInfo::FindPrecachedExternalTypeMapEntry(MethodTable* pGroup STANDARD_VM_CONTRACT; _ASSERTE(pGroupType != nullptr); - if (m_externalTypeMaps.IsNull()) + NativeHashtable typeMapTable; + NativeParser namedEntries; + if (!TryGetPrecachedExternalTypeMap(pGroupType, &typeMapTable, &namedEntries)) { return TypeHandle(); } - UINT32 hash = GetVersionResilientTypeHashCode(pGroupType); uint32_t keyLen = (uint32_t)strlen(pKey); UINT32 typeArgHash = ComputeNameHashCode(pKey, keyLen); - NativeHashtable::Enumerator lookup = m_externalTypeMaps.Lookup(hash); - NativeParser entryParser; - while (lookup.GetNext(entryParser)) + NativeHashtable::Enumerator typeMapLookup = typeMapTable.Lookup(typeArgHash); + NativeParser typeMapEntryParser; + while (typeMapLookup.GetNext(typeMapEntryParser)) { - uint32_t groupTypeImportSection = entryParser.GetUnsigned(); - uint32_t groupTypeFixupIndex = entryParser.GetUnsigned(); - TypeHandle groupTypeHandle = GetTypeHandleForNativeFormatFixupReference(this, m_pModule, groupTypeImportSection, groupTypeFixupIndex); - if (groupTypeHandle != TypeHandle(pGroupType)) + if (typeMapEntryParser.StringEquals(pKey, keyLen)) { - continue; + typeMapEntryParser.SkipString(); + uint32_t importSection = typeMapEntryParser.GetUnsigned(); + uint32_t fixupIndex = typeMapEntryParser.GetUnsigned(); + return GetTypeHandleForNativeFormatFixupReference(this, m_pModule, importSection, fixupIndex); } - - if (entryParser.GetUnsigned() == 0) - { - // Table is not valid - return TypeHandle(); - } - - NativeHashtable typeMapTable = NativeHashtable(entryParser); - - NativeHashtable::Enumerator typeMapLookup = typeMapTable.Lookup(typeArgHash); - NativeParser typeMapEntryParser; - while (typeMapLookup.GetNext(typeMapEntryParser)) - { - if (typeMapEntryParser.StringEquals(pKey, keyLen)) - { - typeMapEntryParser.SkipString(); - uint32_t resultImportSection = typeMapEntryParser.GetUnsigned(); - uint32_t resultFixupIndex = typeMapEntryParser.GetUnsigned(); - return GetTypeHandleForNativeFormatFixupReference(this, m_pModule, resultImportSection, resultFixupIndex); - } - } - - // No matching entry found in the table. - return TypeHandle(); } - // No table found for the group type. return TypeHandle(); } @@ -1858,46 +1882,41 @@ bool ReadyToRunInfo::CheckForUniqueExternalTypeMapKeys(MethodTable* pGroupType, { STANDARD_VM_CONTRACT; - _ASSERTE(pGroupType != nullptr); - if (m_externalTypeMaps.IsNull()) + NativeHashtable typeMapTable; + NativeParser namedEntries; + if (!TryGetPrecachedExternalTypeMap(pGroupType, &typeMapTable, &namedEntries)) { return true; } - UINT32 hash = GetVersionResilientTypeHashCode(pGroupType); - NativeHashtable::Enumerator lookup = m_externalTypeMaps.Lookup(hash); - NativeParser entryParser; - while (lookup.GetNext(entryParser)) + NativeHashtable::AllEntriesEnumerator allEntries(&typeMapTable); + for (NativeParser typeMapEntryParser = allEntries.GetNext(); !typeMapEntryParser.IsNull(); typeMapEntryParser = allEntries.GetNext()) { - uint32_t groupTypeImportSection = entryParser.GetUnsigned(); - uint32_t groupTypeFixupIndex = entryParser.GetUnsigned(); - TypeHandle groupTypeHandle = GetTypeHandleForNativeFormatFixupReference(this, m_pModule, groupTypeImportSection, groupTypeFixupIndex); - if (groupTypeHandle != TypeHandle(pGroupType)) - { - continue; - } + LPCUTF8 string; + uint32_t stringLength; + typeMapEntryParser.GetString((PTR_CBYTE*)&string, &stringLength); - if (entryParser.GetUnsigned() == 0) + StringWithLength key = {string, stringLength}; + if (pHash->LookupPtr(key) != nullptr) { - // Table is not valid - return true; + return false; } + pHash->Add(key); + } - NativeHashtable typeMapTable = NativeHashtable(entryParser); - - NativeHashtable::AllEntriesEnumerator allEntries(&typeMapTable); - - for (NativeParser typeMapEntryParser = allEntries.GetNext(); !typeMapEntryParser.IsNull(); typeMapEntryParser = allEntries.GetNext()) + if (!namedEntries.IsNull()) + { + uint32_t count = namedEntries.GetUnsigned(); + for (uint32_t i = 0; i < count; i++) { LPCUTF8 string; uint32_t stringLength; - typeMapEntryParser.GetString((PTR_CBYTE*)&string, &stringLength); + namedEntries.GetString((PTR_CBYTE*)&string, &stringLength); + namedEntries.SkipString(); StringWithLength key = {string, stringLength}; - if (pHash->LookupPtr(key) != nullptr) { - // Hash already contains this key, we found a duplicate. return false; } pHash->Add(key); @@ -1907,29 +1926,19 @@ bool ReadyToRunInfo::CheckForUniqueExternalTypeMapKeys(MethodTable* pGroupType, return true; } +bool ReadyToRunInfo::TryGetPrecachedProxyTypeMap(MethodTable* pGroupType, NativeHashtable* pTypeMap, NativeParser* pNamedEntries) +{ + STANDARD_VM_CONTRACT; + return TryGetPrecachedTypeMap(this, m_pModule, m_proxyTypeMaps, pGroupType, pTypeMap, pNamedEntries); +} + bool ReadyToRunInfo::HasPrecachedProxyTypeMap(MethodTable* pGroupType) { STANDARD_VM_CONTRACT; - _ASSERTE(pGroupType != nullptr); - if (m_proxyTypeMaps.IsNull()) - { - return false; - } - UINT32 hash = GetVersionResilientTypeHashCode(pGroupType); - NativeHashtable::Enumerator lookup = m_proxyTypeMaps.Lookup(hash); - NativeParser entryParser; - while (lookup.GetNext(entryParser)) - { - uint32_t importSection = entryParser.GetUnsigned(); - uint32_t fixupIndex = entryParser.GetUnsigned(); - TypeHandle typeHandle = GetTypeHandleForNativeFormatFixupReference(this, m_pModule, importSection, fixupIndex); - if (typeHandle == TypeHandle(pGroupType)) - { - // A non-zero value next in the entry indicates that the table is valid. - return entryParser.GetUnsigned() != 0; - } - } - return false; + + NativeHashtable typeMap; + NativeParser namedEntries; + return TryGetPrecachedProxyTypeMap(pGroupType, &typeMap, &namedEntries); } TypeHandle ReadyToRunInfo::FindPrecachedProxyTypeMapEntry(MethodTable* pGroupType, TypeHandle key) @@ -1937,55 +1946,31 @@ TypeHandle ReadyToRunInfo::FindPrecachedProxyTypeMapEntry(MethodTable* pGroupTyp STANDARD_VM_CONTRACT; _ASSERTE(pGroupType != nullptr); - if (m_proxyTypeMaps.IsNull()) + NativeHashtable typeMapTable; + NativeParser namedEntries; + if (!TryGetPrecachedProxyTypeMap(pGroupType, &typeMapTable, &namedEntries)) { return TypeHandle(); } - UINT32 hash = GetVersionResilientTypeHashCode(pGroupType); - NativeHashtable::Enumerator lookup = m_proxyTypeMaps.Lookup(hash); - NativeParser entryParser; - while (lookup.GetNext(entryParser)) + UINT32 typeArgHash = GetVersionResilientTypeHashCode(key); + NativeHashtable::Enumerator typeMapLookup = typeMapTable.Lookup(typeArgHash); + NativeParser typeMapEntryParser; + while (typeMapLookup.GetNext(typeMapEntryParser)) { - uint32_t groupTypeImportSection = entryParser.GetUnsigned(); - uint32_t groupTypeFixupIndex = entryParser.GetUnsigned(); - TypeHandle groupTypeHandle = GetTypeHandleForNativeFormatFixupReference(this, m_pModule, groupTypeImportSection, groupTypeFixupIndex); - if (groupTypeHandle != TypeHandle(pGroupType)) + uint32_t keyImportSection = typeMapEntryParser.GetUnsigned(); + uint32_t keyFixupIndex = typeMapEntryParser.GetUnsigned(); + TypeHandle keyTypeHandle = GetTypeHandleForNativeFormatFixupReference(this, m_pModule, keyImportSection, keyFixupIndex); + if (keyTypeHandle != key) { continue; } - if (entryParser.GetUnsigned() == 0) - { - // Table is not valid - return TypeHandle(); - } - - NativeHashtable typeMapTable = NativeHashtable(entryParser); - - UINT32 typeArgHash = GetVersionResilientTypeHashCode(key); - NativeHashtable::Enumerator typeMapLookup = typeMapTable.Lookup(typeArgHash); - NativeParser typeMapEntryParser; - while (typeMapLookup.GetNext(typeMapEntryParser)) - { - uint32_t keyImportSection = typeMapEntryParser.GetUnsigned(); - uint32_t keyFixupIndex = typeMapEntryParser.GetUnsigned(); - TypeHandle keyTypeHandle = GetTypeHandleForNativeFormatFixupReference(this, m_pModule, keyImportSection, keyFixupIndex); - if (keyTypeHandle != key) - { - continue; - } - - uint32_t resultImportSection = typeMapEntryParser.GetUnsigned(); - uint32_t resultFixupIndex = typeMapEntryParser.GetUnsigned(); - return GetTypeHandleForNativeFormatFixupReference(this, m_pModule, resultImportSection, resultFixupIndex); - } - - // No matching entry found in the table. - return TypeHandle(); + uint32_t resultImportSection = typeMapEntryParser.GetUnsigned(); + uint32_t resultFixupIndex = typeMapEntryParser.GetUnsigned(); + return GetTypeHandleForNativeFormatFixupReference(this, m_pModule, resultImportSection, resultFixupIndex); } - // No table found for the group type. return TypeHandle(); } @@ -2765,7 +2750,7 @@ PCODE DynamicHelpers::CreateDictionaryLookupHelper(LoaderAllocator * pAllocator, else { _ASSERTE(pLookup->sizeOffset == CORINFO_NO_SIZE_CHECK); - // SecondIndir is in bytes, but actual indirections into the table are always pointer aligned. + // SecondIndir is in bytes, but actual indirections into the table are always pointer aligned. // A value of 0 indicates that the second indirection is into the first generic dictionary of // the type, which is the most common access pattern for generics. For Dictionary, // a SecondIndir of 0, and a LastIndir of 0 would indicate the MethodTable pointer of TKey, diff --git a/src/coreclr/vm/readytoruninfo.h b/src/coreclr/vm/readytoruninfo.h index c63f5cc1a3dedc..c14c65861b77f6 100644 --- a/src/coreclr/vm/readytoruninfo.h +++ b/src/coreclr/vm/readytoruninfo.h @@ -417,11 +417,13 @@ class ReadyToRunInfo bool MayHaveCustomAttribute(WellKnownAttribute attribute, mdToken token); void DisableCustomAttributeFilter(); + bool TryGetPrecachedExternalTypeMap(MethodTable* pGroupType, NativeFormat::NativeHashtable* pTypeMap, NativeFormat::NativeParser* pNamedEntries); bool HasPrecachedExternalTypeMap(MethodTable* pGroupType); TypeHandle FindPrecachedExternalTypeMapEntry(MethodTable* pGroupType, LPCUTF8 pKey); bool CheckForUniqueExternalTypeMapKeys(MethodTable* pGroupType, ExternalTypeNameHash *pHash); + bool TryGetPrecachedProxyTypeMap(MethodTable* pGroupType, NativeFormat::NativeHashtable* pTypeMap, NativeFormat::NativeParser* pNamedEntries); bool HasPrecachedProxyTypeMap(MethodTable* pGroupType); TypeHandle FindPrecachedProxyTypeMapEntry(MethodTable* pGroupType, TypeHandle key); diff --git a/src/tests/Interop/TypeMap/TypeMapApp.cs b/src/tests/Interop/TypeMap/TypeMapApp.cs index 6d92f3fac52e9c..a294812aa95502 100644 --- a/src/tests/Interop/TypeMap/TypeMapApp.cs +++ b/src/tests/Interop/TypeMap/TypeMapApp.cs @@ -323,11 +323,17 @@ public static void Validate_BlobOnlyAttributeTypeNames() Assert.Equal(typeof(C1), externalMap["blob_only_c1"]); Assert.Equal(typeof(S1), externalMap["blob_only_s1"]); Assert.Equal(typeof(Lib5Type1), externalMap["lib5_type1"]); + Assert.True(externalMap.TryGetValue("lib5_type1", out Type? cachedExternalType)); + Assert.Equal(typeof(Lib5Type1), cachedExternalType); IReadOnlyDictionary proxyMap = TypeMapping.GetOrCreateProxyTypeMapping(); Assert.Equal(typeof(S1), proxyMap[typeof(C1)]); Assert.Equal(typeof(C1), proxyMap[typeof(S1)]); Assert.Equal(typeof(Lib5Proxy1), proxyMap[new Lib5Type1().GetType()]); + Assert.Equal(typeof(C1), proxyMap[new DupType_MapObject().GetType()]); + Assert.Equal(typeof(S1), proxyMap[new DupType_MapString().GetType()]); + Assert.True(proxyMap.TryGetValue(typeof(Lib5Type1), out Type? cachedProxyType)); + Assert.Equal(typeof(Lib5Proxy1), cachedProxyType); } [Fact] diff --git a/src/tests/Interop/TypeMap/TypeMapBlobOnlyLib.il b/src/tests/Interop/TypeMap/TypeMapBlobOnlyLib.il index 3a75d49cff9f85..6b5a374134e013 100644 --- a/src/tests/Interop/TypeMap/TypeMapBlobOnlyLib.il +++ b/src/tests/Interop/TypeMap/TypeMapBlobOnlyLib.il @@ -34,5 +34,15 @@ = {type(class 'Lib5Type1, TypeMapLib5') type(class 'Lib5Proxy1, TypeMapLib5')} + // These source types have the same full name but come from different assemblies, so their + // proxy map entries share a hash bucket and require exact lazy type resolution. + .custom instance void class [System.Runtime.InteropServices]System.Runtime.InteropServices.TypeMapAssociationAttribute`1::.ctor(class [System.Runtime]System.Type, class [System.Runtime]System.Type) + = {type(class 'Lib.AliasedName, TypeMapLib2') + type(class 'C1, TypeMapLib1')} + + .custom instance void class [System.Runtime.InteropServices]System.Runtime.InteropServices.TypeMapAssociationAttribute`1::.ctor(class [System.Runtime]System.Type, class [System.Runtime]System.Type) + = {type(class 'Lib.AliasedName, TypeMapApp') + type(class 'S1, TypeMapLib1')} + .ver 0:0:0:0 } From c29e01b20e5f4656a6ab5818f643a6b95836226c Mon Sep 17 00:00:00 2001 From: Jackson Schuster <36744439+jtschuster@users.noreply.github.com> Date: Tue, 1 Sep 2026 12:59:07 -0700 Subject: [PATCH 2/2] Fix typo and R2R format version reference --- docs/design/coreclr/botr/readytorun-format.md | 2 +- src/coreclr/vm/readytoruninfo.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/design/coreclr/botr/readytorun-format.md b/docs/design/coreclr/botr/readytorun-format.md index 968a33722a0bde..2cf0a339241c75 100644 --- a/docs/design/coreclr/botr/readytorun-format.md +++ b/docs/design/coreclr/botr/readytorun-format.md @@ -782,7 +782,7 @@ State |------:|:-----|:-----------------| | 0 | Runtime attribute fallback | No precomputed map data. The runtime processes the type map attributes instead. | | 1 | Precomputed fixups | `ExternalTypeMapHashtable` only. | -| 2 | Precomputed fixups and type names | `ExternalTypeMapHashtable` followed immediately by `NamedEntries`. Added in V27.1. | +| 2 | Precomputed fixups and type names | `ExternalTypeMapHashtable` followed immediately by `NamedEntries`. Added in V28. | `ExternalTypeMapHashtable` is a native hashtable keyed by the name hash code of the external type map key. Each value contains: diff --git a/src/coreclr/vm/readytoruninfo.cpp b/src/coreclr/vm/readytoruninfo.cpp index 6c1ca101f78358..05287099da5c8e 100644 --- a/src/coreclr/vm/readytoruninfo.cpp +++ b/src/coreclr/vm/readytoruninfo.cpp @@ -1794,7 +1794,7 @@ namespace *pTypeMap = NativeHashtable(entryParser); *pNamedEntries = state == TypeMapState::PrecomputedFixupsAndTypeNames - ? entrpTypeMap->GetParserAfterTable() + ? pTypeMap->GetParserAfterTable() : NativeParser(); return true; }