diff --git a/docs/design/features/typemap.md b/docs/design/features/typemap.md new file mode 100644 index 00000000000000..6501b3b0b33b8a --- /dev/null +++ b/docs/design/features/typemap.md @@ -0,0 +1,223 @@ +# Interop Type Map + +## Background + +When interop between languages/platforms involves the projection of types, some kind of type mapping logic must often exist. This mapping mechanism is used to determine what .NET type should be used to project a type from language X and vice versa. + +The most common mechanism for this is the generation of a large look-up table at build time, which is then injected into the application or Assembly. If injected into the Assembly, there is typically some registration mechanism for the mapping data. Additional modifications and optimizations can be applied based on the user experience or scenarios constraints (that is, build time, execution environment limitations, etc). + +Prior to .NET 10 there were at least three (3) bespoke mechanisms for this in the .NET ecosystem: + +* C#/WinRT - [Built-in mappings](https://github.com/microsoft/CsWinRT/b1733e95c6d35b551fc8cf6fe04e2a0c287346dd/master/src/WinRT.Runtime/Projections.CustomTypeMappings.tt), [Generation of vtables for AOT](https://github.com/microsoft/CsWinRT/blob/b1733e95c6d35b551fc8cf6fe04e2a0c287346dd/src/Authoring/WinRT.SourceGenerator/AotOptimizer.cs#L1597). + +* .NET For Android - [Assembly Store doc](https://github.com/dotnet/android/blob/b8d0669e951d683443c19ecac06dc96363791820/Documentation/project-docs/AssemblyStores.md), [Assembly Store generator](https://github.com/dotnet/android/blob/b8d0669e951d683443c19ecac06dc96363791820/src/Xamarin.Android.Build.Tasks/Utilities/TypeMappingReleaseNativeAssemblyGenerator.cs), [unmanaged Assembly Store types](https://github.com/dotnet/android/blob/b8d0669e951d683443c19ecac06dc96363791820/src/native/xamarin-app-stub/xamarin-app.hh). + +* Objective-C - [Registrar](https://github.com/dotnet/macios/blob/cee75657955e29981ded2fb0c6f0ee832db9a8d3/src/ObjCRuntime/Registrar.cs#L87), [Managed Static Registrar](https://github.com/dotnet/macios/blob/cee75657955e29981ded2fb0c6f0ee832db9a8d3/docs/managed-static-registrar.md). + +## Priorties + +1) Trimmer friendly - AOT compatible. +2) Usable from both managed and unmanaged environments. +3) Low impact to application start-up and/or Assembly load. +4) Be composable - handle multiple type mappings. + +## APIs + +The below .NET APIs represents only part of the feature. The complete scenario would involve additional steps and tooling. + +**Provided by BCL (that is, NetCoreApp)** +```csharp +namespace System.Runtime.InteropServices; + +/// +/// Type mapping between a string and a type. +/// +/// Type universe +[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] +public sealed class TypeMapAttribute : Attribute +{ + /// + /// Create a mapping between a value and a . + /// + /// String representation of key + /// Type value + /// + /// This mapping is unconditionally inserted into the type map. + /// + public TypeMapAttribute(string value, Type target) + { } + + /// + /// Create a mapping between a value and a . + /// + /// String representation of key + /// Type value + /// Type used by Trimmer to determine type map inclusion. + /// + /// This mapping is only included in the type map if the Trimmer observes a type check + /// using the represented by . + /// + [RequiresUnreferencedCode("Interop types may be removed by trimming")] + public TypeMapAttribute(string value, Type target, Type trimTarget) + { } +} + +/// +/// Declare an assembly that should be inspected during type map building. +/// +/// Type universe +[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] +public sealed class TypeMapAssemblyTargetAttribute : Attribute +{ + /// + /// Provide the assembly to look for type mapping attributes. + /// + /// Assembly to reference + public TypeMapAssemblyTargetAttribute(string assemblyName) + { } +} + +/// +/// Create a type association between a type and its proxy. +/// +/// Type universe +[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = false)] +public sealed class TypeMapAssociationAttribute : Attribute +{ + /// + /// Create an association between two types in the type map. + /// + /// Target type. + /// Type to associated with . + /// + /// This mapping will only exist in the type map if the Trimmer observes + /// an allocation using the represented by . + /// + public TypeMapAssociationAttribute(Type source, Type proxy) + { } +} + +/// +/// Entry type for interop type mapping logic. +/// +public static class TypeMapping +{ + /// + /// Returns the External type type map generated for the current application. + /// + /// Type universe + /// Requested type map + /// True if the map is returned, otherwise false. + /// + /// Call sites are treated as an intrinsic by the Trimmer and implemented inline. + /// + [RequiresUnreferencedCode("Interop types may be removed by trimming")] + public static IReadOnlyDictionary GetOrCreateExternalTypeMapping(); + + /// + /// Returns the associated type type map generated for the current application. + /// + /// Type universe + /// Requested type map + /// True if the map is returned, otherwise false. + /// + /// Call sites are treated as an intrinsic by the Trimmer and implemented inline. + /// + [RequiresUnreferencedCode("Interop types may be removed by trimming")] + public static IReadOnlyDictionary GetOrCreateProxyTypeMapping(); +} +``` + +Given the above types the following would take place. + +1. Types involved in unmanaged-to-managed interop operations would be referenced in a +`TypeMapAttribute` assembly attribute that declared the external type system name, a target +type, and optionally a "trim-target" to determine if the target +type should be included in the map. If the `TypeMapAttribute` constructor that doesn't +take a trim-target is used, the "target type" will be treated as the "trim-target". + +2. Types used in a managed-to-unmanaged interop operation would use `TypeMapAssociationAttribute` +to define a conditional link between the source and proxy type. In other words, if the +source is kept, so is the proxy type. If the Trimmer observes an explicit allocation of the source +type, the entry will be inserted into the map. + +3. During application build, source would be generated and injected into the application +that defines appropriate `TypeMapAssemblyTargetAttribute` instances. This attribute would help the +Trimmer know other assemblies to examine for `TypeMapAttribute` and `TypeMapAssociationAttribute` +instances. These linked assemblies could also be used in the non-Trimmed scenario whereby we +avoid creating the map at build-time and create a dynamic map at run-time instead. + +4. The Trimmer will build two maps based on the above attributes from the application reference +closure. + + **(a)** Using `TypeMapAttribute` a map from `string` to target `Type`. + + **(b)** Using `TypeMapAssociationAttribute` a map from `Type` to `Type` (source to proxy). + +> [!IMPORTANT] +> Conflicting key/value mappings are not allowed. + +> [!NOTE] +> The underlying format of the produced maps is implementation-defined. Different .NET form factors may use different formats. +> +> Additionally, it is not guaranteed that the `TypeMapAttribute`, `TypeMapAssociationAttribute`, and `TypeMapAssemblyTargetAttribute` attributes are present in the final image after a trimming tool has been run. + + +5. Trimming tools will consider calls to `TypeMapping.GetOrCreateExternalTypeMapping<>` and +`TypeMapping.GetOrCreateProxyTypeMapping<>` as intrinsics (for example, Java via `JavaTypeMapGroup`). As a result, it is not trim-compatible to call either of these methods with non-fully-instantiated generic (such as a type argument or a type that is instantiated over a type argument). + +## Type Map entry trimming rules + +This section provides the minimum rules for entries to be included in a given type map by a trimming tool (ie. ILLink or NativeAOT). Due to restrictions in some form factors, some trimming tools may include more entries than would be included based on the rules described below. + +The following rules only apply to code that is considered "reachable" from the entry-point method. Code that a trimming tool determines is unreachable does not contribute to determining if a type map entry is preserved. + +### Type Map Assembly Target probing + +The process of building type maps starts at the entry-point method of the app (the `Main` method). The initial entries for the type maps are collected from the assembly containing the entry-point for the app. From that assembly, any assembly names that are mentioned in a `TypeMapAssemblyTargetAttribute` are scanned. This process then repeats for those assemblies until all assemblies transitively referenced by `TypeMapAssemblyTargetAttribute`s have been scanned. + +An assembly name mentioned in the `TypeMapAssemblyTargetAttribute` does not need to map to an `AssemblyRef` row in the module's metadata. As long as a given name can be resolved by the runtime or by whatever trimming tool is run on the application, it can be used. + +### External Type Map + +An entry in an External Type Map is included when the "trim target" type is referenced in one of the following ways: + +- The argument to the `ldtoken` IL instruction. +- The argument to the `unbox` IL instruction. +- The argument to the `unbox.any` IL instruction. +- The argument to the `isinst` IL instruction. +- The argument to the `castclass` IL instruction. +- The argument to the `box` instruction. +- The argument to the `mkrefany` instruction. +- The argument to the `refanyval` instruction. +- The argument to the `newarr` instruction. +- The argument to the `ldobj` instruction. +- The argument to the `stobj` instruction. +- The argument to the `.constrained` instruction prefix. +- The type of a method argument to the `newobj` instruction. +- The owning type of the method argument to `call`, `callvirt`, `ldftn`, or `ldvirtftn`. + - If the owning type is an interface and the trimming tool can determine that there is only one implementation of the interface, it is free to interpret the method token argument as though it is the method on the only implementing type. +- The generic argument to the `Activator.CreateInstance` method. +- Calls to `Type.GetType` with a constant string representing the type name. + +Many of these instructions can be passed a generic parameter. In that case, the trimming tool should consider type arguments of instantiations of that type as having met one of these rules and include any entries with those types as "trim target" types. + +### Proxy Type Map + +An entry in the Proxy Type Map is included when the "source type" is referenced in one of the following ways: + +- The argument to the `ldtoken` IL instruction when `DynamicallyAccessedMembersAttribute` is specified with one of the flags that preserves constructors for the storage location. +- Calls to `Type.GetType` with a constant string representing the type name when `DynamicallyAccessedMembersAttribute` is specified with one of the flags that preserves constructors for the storage location. +- The type of a method argument to the `newobj` instruction. +- The generic argument to the `Activator.CreateInstance` method. +- The argument to the `box` instruction. +- The argument to the `newarr` instruction. +- The argument to the `.constrained` instruction prefix. +- The argument to the `mkrefany` instruction. +- The argument to the `refanyval` instruction. + +If the type is an interface type and the user could possibly see a `RuntimeTypeHandle` for the type as part of a casting or virtual method resolution operation (such as with `System.Runtime.InteropServices.IDynamicInterfaceCastable`), then the following cases also apply: + +- The argument to the `isinst` IL instruction. +- The argument to the `castclass` IL instruction. +- The owning type of the method argument to `callvirt`, or `ldvirtftn`. diff --git a/eng/testing/linker/trimmingTests.targets b/eng/testing/linker/trimmingTests.targets index 8e6ca49ece3c47..8454ef52940f35 100644 --- a/eng/testing/linker/trimmingTests.targets +++ b/eng/testing/linker/trimmingTests.targets @@ -40,6 +40,7 @@ <_SkippedAppSourceFiles Include="@(TestConsoleAppSourceFiles)" Condition="$([System.String]::Copy('%(TestConsoleAppSourceFiles.SkipOnTestRuntimes)').Contains('$(TargetRid)'))" /> <_SkippedAppSourceFiles Include="@(TestConsoleAppSourceFiles)" Condition="'$(RunNativeAotTestApps)' == 'true' and '%(TestConsoleAppSourceFiles.NativeAotIncompatible)' == 'true'" /> + <_SkippedAppSourceFiles Include="@(TestConsoleAppSourceFiles)" Condition="'$(RunNativeAotTestApps)' != 'true' and '%(TestConsoleAppSourceFiles.NativeAotOnly)' == 'true'" /> <_AppSourceFiles Include="@(TestConsoleAppSourceFiles)" Exclude="@(_SkippedAppSourceFiles)" /> @@ -165,7 +166,7 @@ - + diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/CompatibilitySuppressions.xml b/src/coreclr/nativeaot/System.Private.CoreLib/src/CompatibilitySuppressions.xml index 2de459555d114d..a3fa5bc1c88242 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/CompatibilitySuppressions.xml +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/CompatibilitySuppressions.xml @@ -825,4 +825,8 @@ CP0002 M:System.Diagnostics.DiagnosticMethodInfo.#ctor(System.String,System.String,System.String) - \ No newline at end of file + + CP0001 + T:Internal.NativeFormat.TypeHashingAlgorithms + + diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System.Private.CoreLib.csproj b/src/coreclr/nativeaot/System.Private.CoreLib/src/System.Private.CoreLib.csproj index 8f6fcc441ca226..e91317e9d1f9d3 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System.Private.CoreLib.csproj +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System.Private.CoreLib.csproj @@ -196,6 +196,7 @@ + @@ -322,6 +323,9 @@ Utilities\LockFreeReaderHashtableOfPointers.cs + + Utilities\TypeHashingAlgorithms.cs + System\Collections\Generic\LowLevelList.cs diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/InteropServices/TypeMapLazyDictionary.NativeAot.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/InteropServices/TypeMapLazyDictionary.NativeAot.cs new file mode 100644 index 00000000000000..9784f5d0a5b306 --- /dev/null +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/InteropServices/TypeMapLazyDictionary.NativeAot.cs @@ -0,0 +1,209 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Text; + +using Internal.NativeFormat; +using Internal.Reflection.Core.Execution; +using Internal.Runtime; +using Internal.Runtime.Augments; +using Internal.Runtime.TypeLoader; + +namespace System.Runtime.InteropServices +{ + internal static class TypeMapLazyDictionary + { + public static IReadOnlyDictionary CreateExternalTypeMap(RuntimeType typeMapGroup) + { + RuntimeTypeHandle typeMapGroupHandle = typeMapGroup.TypeHandle; + foreach (TypeManagerHandle module in RuntimeAugments.GetLoadedModules()) + { + if (!TryGetNativeReaderForBlob(module, ReflectionMapBlob.ExternalTypeMap, out NativeReader externalTypeMapReader)) + { + continue; + } + NativeParser externalTypeMapParser = new NativeParser(externalTypeMapReader, 0); + NativeHashtable externalTypeMapTable = new NativeHashtable(externalTypeMapParser); + + ExternalReferencesTable externalReferences = default; + externalReferences.InitializeCommonFixupsTable(module); + + var lookup = externalTypeMapTable.Lookup(typeMapGroupHandle.GetHashCode()); + NativeParser entryParser; + while (!(entryParser = lookup.GetNext()).IsNull) + { + RuntimeTypeHandle foundTypeMapGroup = externalReferences.GetRuntimeTypeHandleFromIndex(entryParser.GetUnsigned()); + if (!foundTypeMapGroup.Equals(typeMapGroupHandle)) + { + continue; + } + bool isValid = entryParser.GetUnsigned() == 1; + if (!isValid) + { + unsafe + { + delegate* exceptionStub = (delegate*)externalReferences.GetFunctionPointerFromIndex(entryParser.GetUnsigned()); + exceptionStub(); + Debug.Fail("Expected exception stub to throw an exception."); + return null; // Should never reach here, as the exception stub should throw an exception. + } + } + + return new ExternalTypeMapDictionary(new NativeHashtable(entryParser), externalReferences); + } + } + + throw ReflectionCoreExecution.ExecutionEnvironment.CreateMissingMetadataException(typeMapGroup); + } + + public static IReadOnlyDictionary CreateProxyTypeMap(RuntimeType typeMapGroup) + { + RuntimeTypeHandle typeMapGroupHandle = typeMapGroup.TypeHandle; + foreach (TypeManagerHandle module in RuntimeAugments.GetLoadedModules()) + { + if (!TryGetNativeReaderForBlob(module, ReflectionMapBlob.ProxyTypeMap, out NativeReader externalTypeMapReader)) + { + continue; + } + NativeParser externalTypeMapParser = new NativeParser(externalTypeMapReader, 0); + NativeHashtable externalTypeMapTable = new NativeHashtable(externalTypeMapParser); + + ExternalReferencesTable externalReferences = default; + externalReferences.InitializeCommonFixupsTable(module); + + var lookup = externalTypeMapTable.Lookup(typeMapGroupHandle.GetHashCode()); + NativeParser entryParser; + while (!(entryParser = lookup.GetNext()).IsNull) + { + RuntimeTypeHandle foundTypeMapGroup = externalReferences.GetRuntimeTypeHandleFromIndex(entryParser.GetUnsigned()); + if (!foundTypeMapGroup.Equals(typeMapGroupHandle)) + { + continue; + } + bool isValid = entryParser.GetUnsigned() == 1; + if (!isValid) + { + unsafe + { + delegate* exceptionStub = (delegate*)externalReferences.GetFunctionPointerFromIndex(entryParser.GetUnsigned()); + exceptionStub(); + Debug.Fail("Expected exception stub to throw an exception."); + return null; // Should never reach here, as the exception stub should throw an exception. + } + } + + return new AssociatedTypeMapDictionary(new NativeHashtable(entryParser), externalReferences); + } + } + + throw ReflectionCoreExecution.ExecutionEnvironment.CreateMissingMetadataException(typeMapGroup); + } + + private static unsafe bool TryGetNativeReaderForBlob(TypeManagerHandle module, ReflectionMapBlob blob, out NativeReader reader) + { + byte* pBlob; + uint cbBlob; + + if (RuntimeImports.RhFindBlob(module, (uint)blob, &pBlob, &cbBlob)) + { + reader = new NativeReader(pBlob, cbBlob); + return true; + } + + reader = default; + return false; + } + + private abstract class TypeMapDictionaryBase : IReadOnlyDictionary + { + public abstract Type this[TKey key] { get; } + public abstract bool TryGetValue(TKey key, [MaybeNullWhen(false)] out Type value); + // Not supported to avoid exposing TypeMap entries in a manner that + // would violate invariants the Trimmer is attempting to enforce. + public IEnumerable Keys => throw new NotSupportedException(); + public IEnumerable Values => throw new NotSupportedException(); + public int Count => throw new NotSupportedException(); + public bool ContainsKey(TKey key) => throw new NotSupportedException(); + public IEnumerator> GetEnumerator() => throw new NotSupportedException(); + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + } + + private sealed class ExternalTypeMapDictionary(NativeHashtable table, ExternalReferencesTable externalReferences) : TypeMapDictionaryBase + { + public override Type this[string key] + { + get + { + if (!TryGetValue(key, out Type? value)) + { + ThrowHelper.ThrowKeyNotFoundException(key); + } + return value; + } + } + + public override bool TryGetValue(string key, [MaybeNullWhen(false)] out Type value) + { + var lookup = table.Lookup(TypeHashingAlgorithms.ComputeNameHashCode(key)); + NativeParser entryParser; + while (!(entryParser = lookup.GetNext()).IsNull) + { + if (entryParser.StringEquals(key)) + { + entryParser.SkipString(); + RuntimeTypeHandle typeHandle = externalReferences.GetRuntimeTypeHandleFromIndex(entryParser.GetUnsigned()); + value = Type.GetTypeFromHandle(typeHandle)!; + return true; + } + } + value = null; + return false; + } + } + + private sealed class AssociatedTypeMapDictionary(NativeHashtable table, ExternalReferencesTable externalReferences) : TypeMapDictionaryBase + { + public override Type this[Type key] + { + get + { + if (!TryGetValue(key, out Type? value)) + { + ThrowHelper.ThrowKeyNotFoundException(key); + } + return value; + } + } + + public override bool TryGetValue(Type key, [MaybeNullWhen(false)] out Type value) + { + RuntimeTypeHandle handle = key.TypeHandle; + if (handle.IsNull) + { + value = null; + return false; + } + + var lookup = table.Lookup(handle.GetHashCode()); + NativeParser entryParser; + while (!(entryParser = lookup.GetNext()).IsNull) + { + RuntimeTypeHandle foundHandle = externalReferences.GetRuntimeTypeHandleFromIndex(entryParser.GetUnsigned()); + if (foundHandle.Equals(handle)) + { + RuntimeTypeHandle targetHandle = externalReferences.GetRuntimeTypeHandleFromIndex(entryParser.GetUnsigned()); + value = Type.GetTypeFromHandle(targetHandle)!; + return true; + } + } + value = null; + return false; + } + } + } +} diff --git a/src/coreclr/nativeaot/System.Private.TypeLoader/src/System.Private.TypeLoader.csproj b/src/coreclr/nativeaot/System.Private.TypeLoader/src/System.Private.TypeLoader.csproj index 4ed2c0fec251ec..367dd4750479e5 100644 --- a/src/coreclr/nativeaot/System.Private.TypeLoader/src/System.Private.TypeLoader.csproj +++ b/src/coreclr/nativeaot/System.Private.TypeLoader/src/System.Private.TypeLoader.csproj @@ -216,7 +216,6 @@ - diff --git a/src/coreclr/tools/Common/Compiler/DependencyAnalysis/SortableDependencyNode.cs b/src/coreclr/tools/Common/Compiler/DependencyAnalysis/SortableDependencyNode.cs index ffb7f9e789fd3a..62297efb6973e0 100644 --- a/src/coreclr/tools/Common/Compiler/DependencyAnalysis/SortableDependencyNode.cs +++ b/src/coreclr/tools/Common/Compiler/DependencyAnalysis/SortableDependencyNode.cs @@ -95,6 +95,8 @@ protected enum ObjectNodeOrder StaticsInfoHashtableNode, ReflectionVirtualInvokeMapNode, ArrayOfEmbeddedPointersNode, + ExternalTypeMapObjectNode, + ProxyTypeMapObjectNode, ExternalReferencesTableNode, StackTraceEmbeddedMetadataNode, StackTraceMethodMappingNode, diff --git a/src/coreclr/tools/Common/Internal/NativeFormat/NativeFormatReader.String.cs b/src/coreclr/tools/Common/Internal/NativeFormat/NativeFormatReader.String.cs index e3b90b71132bf8..f36f0732d23607 100644 --- a/src/coreclr/tools/Common/Internal/NativeFormat/NativeFormatReader.String.cs +++ b/src/coreclr/tools/Common/Internal/NativeFormat/NativeFormatReader.String.cs @@ -24,6 +24,11 @@ public void SkipString() { _offset = _reader.SkipString(_offset); } + + public bool StringEquals(string str) + { + return _reader.StringEquals(_offset, str); + } } internal partial class NativeReader diff --git a/src/coreclr/tools/Common/Internal/NativeFormat/NativeFormatWriter.cs b/src/coreclr/tools/Common/Internal/NativeFormat/NativeFormatWriter.cs index f2f0f33a8c6f25..b7f1886da914c6 100644 --- a/src/coreclr/tools/Common/Internal/NativeFormat/NativeFormatWriter.cs +++ b/src/coreclr/tools/Common/Internal/NativeFormat/NativeFormatWriter.cs @@ -2041,15 +2041,5 @@ internal override void Save(NativeWriter writer) } } } - - public override bool Equals(object obj) - { - throw new NotImplementedException(); - } - - public override int GetHashCode() - { - throw new NotImplementedException(); - } } } diff --git a/src/coreclr/tools/Common/Internal/Runtime/MetadataBlob.cs b/src/coreclr/tools/Common/Internal/Runtime/MetadataBlob.cs index e1a4d90162c2d2..9190062cc0baf1 100644 --- a/src/coreclr/tools/Common/Internal/Runtime/MetadataBlob.cs +++ b/src/coreclr/tools/Common/Internal/Runtime/MetadataBlob.cs @@ -42,5 +42,9 @@ internal enum ReflectionMapBlob StaticsInfoHashtable = 34, GenericMethodsHashtable = 35, ExactMethodInstantiationsHashtable = 36, + + // Type map blobs: + ExternalTypeMap = 40, + ProxyTypeMap = 41, } } diff --git a/src/coreclr/tools/Common/TypeSystem/Common/TypeHashingAlgorithms.cs b/src/coreclr/tools/Common/TypeSystem/Common/TypeHashingAlgorithms.cs index 1b9692037fe94e..04d2857a2b0014 100644 --- a/src/coreclr/tools/Common/TypeSystem/Common/TypeHashingAlgorithms.cs +++ b/src/coreclr/tools/Common/TypeSystem/Common/TypeHashingAlgorithms.cs @@ -5,12 +5,16 @@ // Generic functions to compute the hashcode value of types // --------------------------------------------------------------------------- +using System; using System.Diagnostics; using System.Runtime.CompilerServices; using System.Text; namespace Internal.NativeFormat { +#if SYSTEM_PRIVATE_CORELIB + [CLSCompliant(false)] +#endif public static class TypeHashingAlgorithms { public struct HashCodeBuilder diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/CompilationBuilder.Aot.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/CompilationBuilder.Aot.cs index dae5bc317ae47e..5eb049c74e442b 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/CompilationBuilder.Aot.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/CompilationBuilder.Aot.cs @@ -26,6 +26,7 @@ public partial class CompilationBuilder protected SecurityMitigationOptions _mitigationOptions; protected bool _dehydrate; protected bool _useDwarf5; + protected TypeMapManager _typeMapManager = new UsageBasedTypeMapManager(TypeMapMetadata.Empty); partial void InitializePartial() { @@ -129,6 +130,12 @@ public CompilationBuilder UseDwarf5(bool value) return this; } + public CompilationBuilder UseTypeMapManager(TypeMapManager typeMapManager) + { + _typeMapManager = typeMapManager; + return this; + } + protected PreinitializationManager GetPreinitializationManager() { if (_preinitializationManager == null) diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/HandleCallAction.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/HandleCallAction.cs index 4767523a95bf3b..870a969c7e2b90 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/HandleCallAction.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/HandleCallAction.cs @@ -488,6 +488,36 @@ private partial bool TryHandleIntrinsic ( _diagnosticContext.AddDiagnostic(DiagnosticId.AvoidAssemblyGetFilesInSingleFile, calledMethod.GetDisplayName()); break; + case IntrinsicId.TypeMapping_GetOrCreateExternalTypeMapping: + { + if (calledMethod.Method.Instantiation[0].ContainsSignatureVariables(treatGenericParameterLikeSignatureVariable: true)) + { + // We only support GetOrCreateExternalTypeMapping for a fully specified type. + _diagnosticContext.AddDiagnostic(DiagnosticId.TypeMapGroupTypeCannotBeStaticallyDetermined, + calledMethod.Method.Instantiation[0].GetDisplayName()); + } + else + { + TypeDesc typeMapGroup = calledMethod.Method.Instantiation[0]; + _reflectionMarker.Dependencies.Add(_reflectionMarker.Factory.ExternalTypeMapRequest(typeMapGroup), "TypeMapping.GetOrCreateExternalTypeMapping called on type"); + } + break; + } + case IntrinsicId.TypeMapping_GetOrCreateProxyTypeMapping: + { + if (calledMethod.Method.Instantiation[0].ContainsSignatureVariables(treatGenericParameterLikeSignatureVariable: true)) + { + // We only support GetOrCreateProxyTypeMapping for a fully specified type. + _diagnosticContext.AddDiagnostic(DiagnosticId.TypeMapGroupTypeCannotBeStaticallyDetermined, + calledMethod.Method.Instantiation[0].GetDisplayName()); + } + else + { + TypeDesc typeMapGroup = calledMethod.Method.Instantiation[0]; + _reflectionMarker.Dependencies.Add(_reflectionMarker.Factory.ProxyTypeMapRequest(typeMapGroup), "TypeMapping.GetOrCreateProxyTypeMapping called on type"); + } + break; + } default: return false; } diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/AnalyzedExternalTypeMapNode.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/AnalyzedExternalTypeMapNode.cs new file mode 100644 index 00000000000000..b62b3da716dc28 --- /dev/null +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/AnalyzedExternalTypeMapNode.cs @@ -0,0 +1,62 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.Text; +using ILCompiler.DependencyAnalysisFramework; +using Internal.NativeFormat; +using Internal.TypeSystem; + +namespace ILCompiler.DependencyAnalysis +{ + internal sealed class AnalyzedExternalTypeMapNode(TypeDesc typeMapGroup, IReadOnlyDictionary entries) : DependencyNodeCore, IExternalTypeMapNode + { + public TypeDesc TypeMapGroup => typeMapGroup; + + public Vertex CreateTypeMap(NodeFactory factory, NativeWriter writer, Section section, ExternalReferencesTableNode externalReferences) + { + VertexHashtable typeMapHashTable = new(); + + foreach ((string key, TypeDesc type) in entries) + { + Vertex keyVertex = writer.GetStringConstant(key); + Vertex valueVertex = writer.GetUnsignedConstant(externalReferences.GetIndex(factory.MaximallyConstructableType(type))); + Vertex entry = writer.GetTuple(keyVertex, valueVertex); + typeMapHashTable.Append((uint)TypeHashingAlgorithms.ComputeNameHashCode(key), section.Place(entry)); + } + + Vertex typeMapStateVertex = writer.GetUnsignedConstant(1); // Valid type map state + Vertex typeMapGroupVertex = writer.GetUnsignedConstant(externalReferences.GetIndex(factory.NecessaryTypeSymbol(TypeMapGroup))); + Vertex tuple = writer.GetTuple(typeMapGroupVertex, typeMapStateVertex, typeMapHashTable); + return section.Place(tuple); + } + + public override IEnumerable GetConditionalStaticDependencies(NodeFactory context) => []; + public override IEnumerable GetStaticDependencies(NodeFactory context) + { + foreach (TypeDesc targetType in entries.Values) + { + yield return new DependencyListEntry(context.MaximallyConstructableType(targetType), "Analyzed external type map entry target type"); + } + } + public override IEnumerable SearchDynamicDependencies(List> markedNodes, int firstNode, NodeFactory context) => []; + protected override string GetName(NodeFactory context) => $"Analyzed External Type Map: {TypeMapGroup}"; + public IExternalTypeMapNode ToAnalysisBasedNode(NodeFactory factory) => this; + + public override bool InterestingForDynamicDependencyAnalysis => false; + + public override bool HasDynamicDependencies => false; + + public override bool HasConditionalStaticDependencies => false; + + public override bool StaticDependenciesAreComputed => true; + public int ClassCode => -874354558; + + public int CompareToImpl(ISortableNode other, CompilerComparer comparer) + { + AnalyzedExternalTypeMapNode otherEntry = (AnalyzedExternalTypeMapNode)other; + return comparer.Compare(TypeMapGroup, otherEntry.TypeMapGroup); + } + } +} diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/AnalyzedProxyTypeMapNode.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/AnalyzedProxyTypeMapNode.cs new file mode 100644 index 00000000000000..7d19848d23ebb0 --- /dev/null +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/AnalyzedProxyTypeMapNode.cs @@ -0,0 +1,63 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.Text; +using ILCompiler.DependencyAnalysis; +using ILCompiler.DependencyAnalysisFramework; +using Internal.NativeFormat; +using Internal.TypeSystem; + +namespace ILCompiler.DependencyAnalysis +{ + internal sealed class AnalyzedProxyTypeMapNode(TypeDesc typeMapGroup, IReadOnlyDictionary entries) : DependencyNodeCore, IProxyTypeMapNode + { + public TypeDesc TypeMapGroup => typeMapGroup; + public Vertex CreateTypeMap(NodeFactory factory, NativeWriter writer, Section section, ExternalReferencesTableNode externalReferences) + { + VertexHashtable typeMapHashTable = new(); + + foreach ((TypeDesc key, TypeDesc type) in entries) + { + Vertex keyVertex = writer.GetUnsignedConstant(externalReferences.GetIndex(factory.MaximallyConstructableType(key))); + Vertex valueVertex = writer.GetUnsignedConstant(externalReferences.GetIndex(factory.MaximallyConstructableType(type))); + Vertex entry = writer.GetTuple(keyVertex, valueVertex); + typeMapHashTable.Append((uint)key.GetHashCode(), section.Place(entry)); + } + + Vertex typeMapStateVertex = writer.GetUnsignedConstant(1); // Valid type map state + Vertex typeMapGroupVertex = writer.GetUnsignedConstant(externalReferences.GetIndex(factory.NecessaryTypeSymbol(TypeMapGroup))); + Vertex tuple = writer.GetTuple(typeMapGroupVertex, typeMapStateVertex, typeMapHashTable); + return section.Place(tuple); + } + + public override IEnumerable GetConditionalStaticDependencies(NodeFactory context) => []; + public override IEnumerable GetStaticDependencies(NodeFactory context) + { + foreach (var (sourceType, proxyType) in entries) + { + yield return new DependencyListEntry(context.MaximallyConstructableType(sourceType), "Analyzed proxy type map entry source type"); + yield return new DependencyListEntry(context.MaximallyConstructableType(proxyType), "Analyzed proxy type map entry proxy type"); + } + } + public override IEnumerable SearchDynamicDependencies(List> markedNodes, int firstNode, NodeFactory context) => []; + protected override string GetName(NodeFactory context) => $"Analyzed Proxy Type Map: {typeMapGroup}"; + public IProxyTypeMapNode ToAnalysisBasedNode(NodeFactory factory) => this; + public override bool InterestingForDynamicDependencyAnalysis => false; + + public override bool HasDynamicDependencies => false; + + public override bool HasConditionalStaticDependencies => false; + + public override bool StaticDependenciesAreComputed => true; + + public int ClassCode => 171742984; + + public int CompareToImpl(ISortableNode other, CompilerComparer comparer) + { + AnalyzedProxyTypeMapNode otherEntry = (AnalyzedProxyTypeMapNode)other; + return comparer.Compare(TypeMapGroup, otherEntry.TypeMapGroup); + } + } +} diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/CustomAttributeBasedDependencyAlgorithm.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/CustomAttributeBasedDependencyAlgorithm.cs index a18251e401fc03..936f900bb0002a 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/CustomAttributeBasedDependencyAlgorithm.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/CustomAttributeBasedDependencyAlgorithm.cs @@ -118,6 +118,9 @@ private static void AddDependenciesDueToCustomAttributes(ref DependencyList depe { MethodDesc constructor = module.GetMethod(attribute.Constructor); + if (TypeMapManager.LookupTypeMapType(constructor.OwningType) != TypeMapManager.TypeMapAttributeKind.None) + continue; + if (!mdManager.GeneratesAttributeMetadata(constructor.OwningType)) continue; diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ExternalTypeMapNode.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ExternalTypeMapNode.cs new file mode 100644 index 00000000000000..c17781ff8d3c47 --- /dev/null +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ExternalTypeMapNode.cs @@ -0,0 +1,98 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Diagnostics; +using ILCompiler.DependencyAnalysisFramework; +using Internal.NativeFormat; +using Internal.TypeSystem; + +namespace ILCompiler.DependencyAnalysis +{ + internal sealed class ExternalTypeMapNode : DependencyNodeCore, IExternalTypeMapNode + { + private readonly IEnumerable> _mapEntries; + + public ExternalTypeMapNode(TypeDesc typeMapGroup, IEnumerable> mapEntries) + { + _mapEntries = mapEntries; + TypeMapGroup = typeMapGroup; + } + + public override bool InterestingForDynamicDependencyAnalysis => false; + + public override bool HasDynamicDependencies => false; + + public override bool HasConditionalStaticDependencies => true; + + public override bool StaticDependenciesAreComputed => true; + + public TypeDesc TypeMapGroup { get; } + + public override IEnumerable GetConditionalStaticDependencies(NodeFactory context) + { + foreach (var entry in _mapEntries) + { + var (targetType, trimmingTargetType) = entry.Value; + yield return new CombinedDependencyListEntry( + context.MaximallyConstructableType(targetType), + context.NecessaryTypeSymbol(trimmingTargetType), + "Type in external type map is cast target"); + } + } + + public override IEnumerable GetStaticDependencies(NodeFactory context) => []; + + public override IEnumerable SearchDynamicDependencies(List> markedNodes, int firstNode, NodeFactory context) => Array.Empty(); + protected override string GetName(NodeFactory context) => $"External type map: {TypeMapGroup}"; + + public int ClassCode => -785190502; + + public int CompareToImpl(ISortableNode other, CompilerComparer comparer) + { + ExternalTypeMapNode otherEntry = (ExternalTypeMapNode)other; + return comparer.Compare(TypeMapGroup, otherEntry.TypeMapGroup); + } + + private IEnumerable<(string Name, IEETypeNode target)> GetMarkedEntries(NodeFactory factory) + { + foreach (var entry in _mapEntries) + { + var (targetType, trimmingTargetType) = entry.Value; + + if (factory.NecessaryTypeSymbol(trimmingTargetType).Marked) + { + IEETypeNode targetNode = factory.MaximallyConstructableType(targetType); + Debug.Assert(targetNode.Marked); + yield return (entry.Key, targetNode); + } + } + } + + public Vertex CreateTypeMap(NodeFactory factory, NativeWriter writer, Section section, ExternalReferencesTableNode externalReferences) + { + VertexHashtable typeMapHashTable = new(); + + foreach ((string key, IEETypeNode valueNode) in GetMarkedEntries(factory)) + { + Vertex keyVertex = writer.GetStringConstant(key); + Vertex valueVertex = writer.GetUnsignedConstant(externalReferences.GetIndex(valueNode)); + Vertex entry = writer.GetTuple(keyVertex, valueVertex); + typeMapHashTable.Append((uint)TypeHashingAlgorithms.ComputeNameHashCode(key), section.Place(entry)); + } + + Vertex typeMapStateVertex = writer.GetUnsignedConstant(1); // Valid type map state + Vertex typeMapGroupVertex = writer.GetUnsignedConstant(externalReferences.GetIndex(factory.NecessaryTypeSymbol(TypeMapGroup))); + Vertex tuple = writer.GetTuple(typeMapGroupVertex, typeMapStateVertex, typeMapHashTable); + return section.Place(tuple); + } + + public IExternalTypeMapNode ToAnalysisBasedNode(NodeFactory factory) + => new AnalyzedExternalTypeMapNode( + TypeMapGroup, + GetMarkedEntries(factory) + .ToImmutableDictionary(p => p.Name, p => p.target.Type)); + } +} diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ExternalTypeMapObjectNode.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ExternalTypeMapObjectNode.cs new file mode 100644 index 00000000000000..8f72dee452374f --- /dev/null +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ExternalTypeMapObjectNode.cs @@ -0,0 +1,57 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using ILCompiler.DependencyAnalysis; +using ILCompiler.DependencyAnalysisFramework; +using Internal.NativeFormat; +using Internal.Text; +using Internal.TypeSystem; + +namespace ILCompiler.DependencyAnalysis +{ + internal sealed class ExternalTypeMapObjectNode(ExternalReferencesTableNode externalReferences) : ObjectNode, ISymbolDefinitionNode, INodeWithSize + { + public override ObjectData GetData(NodeFactory factory, bool relocsOnly = false) + { + // This node does not trigger generation of other nodes. + if (relocsOnly) + return new ObjectData(Array.Empty(), Array.Empty(), 1, [this]); + + var writer = new NativeWriter(); + var typeMapGroupHashTable = new VertexHashtable(); + + Section hashTableSection = writer.NewSection(); + hashTableSection.Place(typeMapGroupHashTable); + + foreach (IExternalTypeMapNode externalTypeMap in factory.TypeMapManager.GetExternalTypeMaps()) + { + typeMapGroupHashTable.Append((uint)externalTypeMap.TypeMapGroup.GetHashCode(), externalTypeMap.CreateTypeMap(factory, writer, hashTableSection, externalReferences)); + } + + byte[] hashTableBytes = writer.Save(); + + Size = hashTableBytes.Length; + + return new ObjectData(hashTableBytes, Array.Empty(), 1, [this]); + } + public void AppendMangledName(NameMangler nameMangler, Utf8StringBuilder sb) + { + sb.Append(nameMangler.CompilationUnitPrefix).Append("__external_type_map__"u8); + } + + public int Size { get; private set; } + public int Offset => 0; + public override bool IsShareable => false; + public override ObjectNodeSection GetSection(NodeFactory factory) => externalReferences.GetSection(factory); + protected internal override int Phase => (int)ObjectNodePhase.Ordered; + + public override int ClassCode => (int)ObjectNodeOrder.ExternalTypeMapObjectNode; + + public override bool StaticDependenciesAreComputed => true; + + protected override string GetName(NodeFactory context) => "External Type Map Hash Table"; + } +} diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ExternalTypeMapRequestNode.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ExternalTypeMapRequestNode.cs new file mode 100644 index 00000000000000..46bec73bd78a52 --- /dev/null +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ExternalTypeMapRequestNode.cs @@ -0,0 +1,28 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using ILCompiler.DependencyAnalysisFramework; +using Internal.TypeSystem; +using Debug = System.Diagnostics.Debug; + +namespace ILCompiler.DependencyAnalysis +{ + public sealed class ExternalTypeMapRequestNode(TypeDesc typeMapGroup) : DependencyNodeCore + { + public TypeDesc TypeMapGroup { get; } = typeMapGroup; + public override bool InterestingForDynamicDependencyAnalysis => false; + + public override bool HasDynamicDependencies => false; + + public override bool HasConditionalStaticDependencies => false; + + public override bool StaticDependenciesAreComputed => true; + + public override IEnumerable GetConditionalStaticDependencies(NodeFactory context) => []; + public override IEnumerable GetStaticDependencies(NodeFactory context) => []; + public override IEnumerable SearchDynamicDependencies(List> markedNodes, int firstNode, NodeFactory context) => []; + protected override string GetName(NodeFactory context) => $"External type map request: {TypeMapGroup}"; + } +} diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/IExternalTypeMapNode.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/IExternalTypeMapNode.cs new file mode 100644 index 00000000000000..32e059da731210 --- /dev/null +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/IExternalTypeMapNode.cs @@ -0,0 +1,21 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.Text; +using ILCompiler.DependencyAnalysisFramework; +using Internal.NativeFormat; +using Internal.TypeSystem; + +namespace ILCompiler.DependencyAnalysis +{ + public interface IExternalTypeMapNode : IDependencyNode, ISortableNode + { + TypeDesc TypeMapGroup { get; } + + Vertex CreateTypeMap(NodeFactory factory, NativeWriter writer, Section section, ExternalReferencesTableNode externalReferences); + + IExternalTypeMapNode ToAnalysisBasedNode(NodeFactory factory); + } +} diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ILScanNodeFactory.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ILScanNodeFactory.cs index 2d13f181a62242..d780d5e99891cf 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ILScanNodeFactory.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ILScanNodeFactory.cs @@ -12,8 +12,8 @@ namespace ILCompiler.DependencyAnalysis /// public sealed class ILScanNodeFactory : NodeFactory { - public ILScanNodeFactory(CompilerTypeSystemContext context, CompilationModuleGroup compilationModuleGroup, MetadataManager metadataManager, InteropStubManager interopStubManager, NameMangler nameMangler, PreinitializationManager preinitManager) - : base(context, compilationModuleGroup, metadataManager, interopStubManager, nameMangler, new LazyGenericsDisabledPolicy(), new LazyVTableSliceProvider(), new LazyDictionaryLayoutProvider(), new InlinedThreadStatics(), new ExternSymbolsImportedNodeProvider(), preinitManager, new DevirtualizationManager(), ObjectDataInterner.Null) + public ILScanNodeFactory(CompilerTypeSystemContext context, CompilationModuleGroup compilationModuleGroup, MetadataManager metadataManager, InteropStubManager interopStubManager, NameMangler nameMangler, PreinitializationManager preinitManager, TypeMapManager typeMapManager) + : base(context, compilationModuleGroup, metadataManager, interopStubManager, nameMangler, new LazyGenericsDisabledPolicy(), new LazyVTableSliceProvider(), new LazyDictionaryLayoutProvider(), new InlinedThreadStatics(), new ExternSymbolsImportedNodeProvider(), preinitManager, new DevirtualizationManager(), ObjectDataInterner.Null, typeMapManager) { } diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/IProxyTypeMapNode.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/IProxyTypeMapNode.cs new file mode 100644 index 00000000000000..331ec8357b6f71 --- /dev/null +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/IProxyTypeMapNode.cs @@ -0,0 +1,21 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.Text; +using ILCompiler.DependencyAnalysisFramework; +using Internal.NativeFormat; +using Internal.TypeSystem; + +namespace ILCompiler.DependencyAnalysis +{ + public interface IProxyTypeMapNode : IDependencyNode, ISortableNode + { + TypeDesc TypeMapGroup { get; } + + Vertex CreateTypeMap(NodeFactory factory, NativeWriter writer, Section section, ExternalReferencesTableNode externalReferences); + + IProxyTypeMapNode ToAnalysisBasedNode(NodeFactory factor); + } +} diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/InvalidExternalTypeMapNode.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/InvalidExternalTypeMapNode.cs new file mode 100644 index 00000000000000..4674e713ec7a38 --- /dev/null +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/InvalidExternalTypeMapNode.cs @@ -0,0 +1,52 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.Text; +using ILCompiler.DependencyAnalysisFramework; +using Internal.NativeFormat; +using Internal.TypeSystem; + +namespace ILCompiler.DependencyAnalysis +{ + internal sealed class InvalidExternalTypeMapNode(TypeDesc typeMapGroup, MethodDesc throwingMethodStub) : DependencyNodeCore, ISortableNode, IExternalTypeMapNode + { + public override bool InterestingForDynamicDependencyAnalysis => false; + + public override bool HasDynamicDependencies => false; + + public override bool HasConditionalStaticDependencies => false; + + public override bool StaticDependenciesAreComputed => true; + + public override IEnumerable GetConditionalStaticDependencies(NodeFactory context) => Array.Empty(); + public override IEnumerable GetStaticDependencies(NodeFactory context) + { + return [ + new DependencyListEntry(context.MethodEntrypoint(ThrowingMethodStub), "Throwing method stub for invalid type map"), + ]; + } + + public override IEnumerable SearchDynamicDependencies(List> markedNodes, int firstNode, NodeFactory context) => Array.Empty(); + protected override string GetName(NodeFactory context) => $"Invalid external type map: {TypeMapGroup}"; + + public TypeDesc TypeMapGroup { get; } = typeMapGroup; + public MethodDesc ThrowingMethodStub { get; } = throwingMethodStub; + + public int ClassCode => 36910224; + + public int CompareToImpl(ISortableNode other, CompilerComparer comparer) => comparer.Compare(TypeMapGroup, ((InvalidExternalTypeMapNode)other).TypeMapGroup); + + public Vertex CreateTypeMap(NodeFactory factory, NativeWriter writer, Section section, ExternalReferencesTableNode externalReferences) + { + Vertex typeMapStateVertex = writer.GetUnsignedConstant(0); // Invalid type map state + Vertex typeMapGroupVertex = writer.GetUnsignedConstant(externalReferences.GetIndex(factory.NecessaryTypeSymbol(TypeMapGroup))); + Vertex throwingMethodStubVertex = writer.GetUnsignedConstant(externalReferences.GetIndex(factory.MethodEntrypoint(ThrowingMethodStub))); + Vertex tuple = writer.GetTuple(typeMapGroupVertex, typeMapStateVertex, throwingMethodStubVertex); + return section.Place(tuple); + } + + public IExternalTypeMapNode ToAnalysisBasedNode(NodeFactory factory) => new InvalidExternalTypeMapNode(TypeMapGroup, ThrowingMethodStub); + } +} diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/InvalidProxyTypeMapNode.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/InvalidProxyTypeMapNode.cs new file mode 100644 index 00000000000000..a929fbb205ac8e --- /dev/null +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/InvalidProxyTypeMapNode.cs @@ -0,0 +1,51 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.Text; +using ILCompiler.DependencyAnalysisFramework; +using Internal.NativeFormat; +using Internal.TypeSystem; + +namespace ILCompiler.DependencyAnalysis +{ + internal sealed class InvalidProxyTypeMapNode(TypeDesc typeMapGroup, MethodDesc throwingMethodStub) : DependencyNodeCore, IProxyTypeMapNode + { + public TypeDesc TypeMapGroup { get; } = typeMapGroup; + public MethodDesc ThrowingMethodStub { get; } = throwingMethodStub; + + public override bool InterestingForDynamicDependencyAnalysis => false; + + public override bool HasDynamicDependencies => false; + + public override bool HasConditionalStaticDependencies => false; + + public override bool StaticDependenciesAreComputed => true; + + public override IEnumerable GetConditionalStaticDependencies(NodeFactory context) => Array.Empty(); + public override IEnumerable GetStaticDependencies(NodeFactory context) + { + return [ + new DependencyListEntry(context.MethodEntrypoint(ThrowingMethodStub), "Throwing method stub for invalid type map"), + ]; + } + + public override IEnumerable SearchDynamicDependencies(List> markedNodes, int firstNode, NodeFactory context) => Array.Empty(); + protected override string GetName(NodeFactory context) => $"Invalid proxy type map: {TypeMapGroup}"; + + public int ClassCode => 36910224; + + public int CompareToImpl(ISortableNode other, CompilerComparer comparer) => comparer.Compare(TypeMapGroup, ((InvalidProxyTypeMapNode)other).TypeMapGroup); + public Vertex CreateTypeMap(NodeFactory factory, NativeWriter writer, Section section, ExternalReferencesTableNode externalReferences) + { + Vertex typeMapStateVertex = writer.GetUnsignedConstant(0); // Invalid type map state + Vertex typeMapGroupVertex = writer.GetUnsignedConstant(externalReferences.GetIndex(factory.NecessaryTypeSymbol(TypeMapGroup))); + Vertex throwingMethodStubVertex = writer.GetUnsignedConstant(externalReferences.GetIndex(factory.MethodEntrypoint(ThrowingMethodStub))); + Vertex tuple = writer.GetTuple(typeMapGroupVertex, typeMapStateVertex, throwingMethodStubVertex); + return section.Place(tuple); + } + + public IProxyTypeMapNode ToAnalysisBasedNode(NodeFactory factory) => new InvalidProxyTypeMapNode(TypeMapGroup, ThrowingMethodStub); + } +} diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/NodeFactory.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/NodeFactory.cs index 9b73ea95894a3a..a6add520521126 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/NodeFactory.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/NodeFactory.cs @@ -41,7 +41,8 @@ public NodeFactory( ImportedNodeProvider importedNodeProvider, PreinitializationManager preinitializationManager, DevirtualizationManager devirtualizationManager, - ObjectDataInterner dataInterner) + ObjectDataInterner dataInterner, + TypeMapManager typeMapManager) { _target = context.Target; @@ -61,6 +62,7 @@ public NodeFactory( PreinitializationManager = preinitializationManager; DevirtualizationManager = devirtualizationManager; ObjectInterner = dataInterner; + TypeMapManager = typeMapManager; } public void SetMarkingComplete() @@ -130,6 +132,11 @@ internal ObjectDataInterner ObjectInterner get; } + public TypeMapManager TypeMapManager + { + get; + } + /// /// Return true if the type is not permitted by the rules of the runtime to have an MethodTable. /// The implementation here is not intended to be complete, but represents many conditions @@ -581,6 +588,16 @@ private void CreateNodeCaches() return new StringAllocatorMethodNode(constructor); }); + _externalTypeMapRequests = new NodeCache(type => + { + return new ExternalTypeMapRequestNode(type); + }); + + _proxyTypeMapRequests = new NodeCache(type => + { + return new ProxyTypeMapRequestNode(type); + }); + NativeLayout = new NativeLayoutHelper(this); } @@ -1464,6 +1481,20 @@ public StructMarshallingDataNode StructMarshallingData(DefType type) return _structMarshalingDataNodes.GetOrAdd(type); } + private NodeCache _externalTypeMapRequests; + + public ExternalTypeMapRequestNode ExternalTypeMapRequest(TypeDesc type) + { + return _externalTypeMapRequests.GetOrAdd(type); + } + + private NodeCache _proxyTypeMapRequests; + + public ProxyTypeMapRequestNode ProxyTypeMapRequest(TypeDesc type) + { + return _proxyTypeMapRequests.GetOrAdd(type); + } + /// /// Returns alternative symbol name that object writer should produce for given symbols /// in addition to the regular one. @@ -1536,8 +1567,10 @@ public virtual void AttachToDependencyGraph(DependencyAnalyzerBase var commonFixupsTableNode = new ExternalReferencesTableNode("CommonFixupsTable", this); InteropStubManager.AddToReadyToRunHeader(ReadyToRunHeader, this, commonFixupsTableNode); + TypeMapManager.AddToReadyToRunHeader(ReadyToRunHeader, this, commonFixupsTableNode); MetadataManager.AddToReadyToRunHeader(ReadyToRunHeader, this, commonFixupsTableNode); MetadataManager.AttachToDependencyGraph(graph); + TypeMapManager.AttachToDependencyGraph(graph); ReadyToRunHeader.Add(MetadataManager.BlobIdToReadyToRunSection(ReflectionMapBlob.CommonFixupsTable), commonFixupsTableNode); } diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ProxyTypeMapNode.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ProxyTypeMapNode.cs new file mode 100644 index 00000000000000..a131677a776be6 --- /dev/null +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ProxyTypeMapNode.cs @@ -0,0 +1,94 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Diagnostics; +using System.Text; +using ILCompiler.DependencyAnalysis; +using ILCompiler.DependencyAnalysisFramework; +using Internal.NativeFormat; +using Internal.TypeSystem; + +namespace ILCompiler.DependencyAnalysis +{ + internal sealed class ProxyTypeMapNode : DependencyNodeCore, IProxyTypeMapNode + { + private readonly IEnumerable> _mapEntries; + + public ProxyTypeMapNode(TypeDesc typeMapGroup, IEnumerable> mapEntries) + { + _mapEntries = mapEntries; + TypeMapGroup = typeMapGroup; + } + + public TypeDesc TypeMapGroup { get; } + + public IEnumerable> MapEntries => _mapEntries; + public override bool InterestingForDynamicDependencyAnalysis => false; + + public override bool HasDynamicDependencies => false; + + public override bool HasConditionalStaticDependencies => true; + + public override bool StaticDependenciesAreComputed => true; + + public int ClassCode => 779513676; + + public int CompareToImpl(ISortableNode other, CompilerComparer comparer) => comparer.Compare(TypeMapGroup, ((ProxyTypeMapNode)other).TypeMapGroup); + + public override IEnumerable GetConditionalStaticDependencies(NodeFactory context) + { + foreach (var (key, value) in _mapEntries) + { + yield return new CombinedDependencyListEntry( + context.MaximallyConstructableType(value), + context.MaximallyConstructableType(key), + "Proxy type map entry"); + } + } + + public override IEnumerable GetStaticDependencies(NodeFactory context) => Array.Empty(); + public override IEnumerable SearchDynamicDependencies(List> markedNodes, int firstNode, NodeFactory context) => Array.Empty(); + protected override string GetName(NodeFactory context) => $"Proxy type map: {TypeMapGroup}"; + + private IEnumerable<(IEETypeNode key, IEETypeNode value)> GetMarkedEntries(NodeFactory factory) + { + foreach (var (key, value) in MapEntries) + { + IEETypeNode keyNode = factory.MaximallyConstructableType(key); + if (keyNode.Marked) + { + IEETypeNode valueNode = factory.MaximallyConstructableType(value); + Debug.Assert(valueNode.Marked); + yield return (keyNode, valueNode); + } + } + } + + public Vertex CreateTypeMap(NodeFactory factory, NativeWriter writer, Section section, ExternalReferencesTableNode externalReferences) + { + VertexHashtable typeMapHashTable = new VertexHashtable(); + + foreach ((IEETypeNode keyNode, IEETypeNode valueNode) in GetMarkedEntries(factory)) + { + Vertex keyVertex = writer.GetUnsignedConstant(externalReferences.GetIndex(keyNode)); + Vertex valueVertex = writer.GetUnsignedConstant(externalReferences.GetIndex(valueNode)); + Vertex entry = writer.GetTuple(keyVertex, valueVertex); + typeMapHashTable.Append((uint)keyNode.Type.GetHashCode(), section.Place(entry)); + } + + Vertex typeMapStateVertex = writer.GetUnsignedConstant(1); // Valid type map state + Vertex typeMapGroupVertex = writer.GetUnsignedConstant(externalReferences.GetIndex(factory.NecessaryTypeSymbol(TypeMapGroup))); + Vertex tuple = writer.GetTuple(typeMapGroupVertex, typeMapStateVertex, typeMapHashTable); + return section.Place(tuple); + } + + public IProxyTypeMapNode ToAnalysisBasedNode(NodeFactory factory) + => new AnalyzedProxyTypeMapNode( + TypeMapGroup, + GetMarkedEntries(factory) + .ToImmutableDictionary(p => p.key.Type, p => p.value.Type)); + } +} diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ProxyTypeMapObjectNode.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ProxyTypeMapObjectNode.cs new file mode 100644 index 00000000000000..cdca8804ba5a80 --- /dev/null +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ProxyTypeMapObjectNode.cs @@ -0,0 +1,59 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Formats.Tar; +using ILCompiler.DependencyAnalysis; +using ILCompiler.DependencyAnalysisFramework; +using Internal.NativeFormat; +using Internal.Text; +using Internal.TypeSystem; + +namespace ILCompiler.DependencyAnalysis +{ + internal sealed class ProxyTypeMapObjectNode(ExternalReferencesTableNode externalReferences) : ObjectNode, ISymbolDefinitionNode, INodeWithSize + { + public override ObjectData GetData(NodeFactory factory, bool relocsOnly = false) + { + // This node does not trigger generation of other nodes. + if (relocsOnly) + return new ObjectData(Array.Empty(), Array.Empty(), 1, [this]); + + var writer = new NativeWriter(); + var typeMapGroupHashTable = new VertexHashtable(); + + Section hashTableSection = writer.NewSection(); + hashTableSection.Place(typeMapGroupHashTable); + + foreach (IProxyTypeMapNode proxyTypeMap in factory.TypeMapManager.GetProxyTypeMaps()) + { + TypeDesc typeMapGroup = proxyTypeMap.TypeMapGroup; + typeMapGroupHashTable.Append((uint)typeMapGroup.GetHashCode(), proxyTypeMap.CreateTypeMap(factory, writer, hashTableSection, externalReferences)); + } + + byte[] hashTableBytes = writer.Save(); + + Size = hashTableBytes.Length; + + return new ObjectData(hashTableBytes, Array.Empty(), 1, [this]); + } + public void AppendMangledName(NameMangler nameMangler, Utf8StringBuilder sb) + { + sb.Append(nameMangler.CompilationUnitPrefix).Append("__proxy_type_map__"u8); + } + + public int Size { get; private set; } + public int Offset => 0; + public override bool IsShareable => false; + public override ObjectNodeSection GetSection(NodeFactory factory) => externalReferences.GetSection(factory); + protected internal override int Phase => (int)ObjectNodePhase.Ordered; + + public override int ClassCode => (int)ObjectNodeOrder.ProxyTypeMapObjectNode; + + public override bool StaticDependenciesAreComputed => true; + + protected override string GetName(NodeFactory context) => "Proxy Type Map Hash Table"; + } +} diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ProxyTypeMapRequestNode.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ProxyTypeMapRequestNode.cs new file mode 100644 index 00000000000000..ebfb9a412c7a24 --- /dev/null +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ProxyTypeMapRequestNode.cs @@ -0,0 +1,28 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using ILCompiler.DependencyAnalysisFramework; +using Internal.TypeSystem; +using Debug = System.Diagnostics.Debug; + +namespace ILCompiler.DependencyAnalysis +{ + public sealed class ProxyTypeMapRequestNode(TypeDesc typeMapGroup) : DependencyNodeCore + { + public TypeDesc TypeMapGroup { get; } = typeMapGroup; + public override bool InterestingForDynamicDependencyAnalysis => false; + + public override bool HasDynamicDependencies => false; + + public override bool HasConditionalStaticDependencies => false; + + public override bool StaticDependenciesAreComputed => true; + + public override IEnumerable GetConditionalStaticDependencies(NodeFactory context) => []; + public override IEnumerable GetStaticDependencies(NodeFactory context) => []; + public override IEnumerable SearchDynamicDependencies(List> markedNodes, int firstNode, NodeFactory context) => []; + protected override string GetName(NodeFactory context) => $"Proxy type map request: {TypeMapGroup}"; + } +} diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ILScanner.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ILScanner.cs index efad0516f5a354..309772d6e5ef72 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ILScanner.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ILScanner.cs @@ -16,6 +16,7 @@ using Internal.ReadyToRunConstants; using Debug = System.Diagnostics.Debug; +using Internal.NativeFormat; namespace ILCompiler { @@ -275,6 +276,11 @@ public ReadOnlyFieldPolicy GetReadOnlyFieldPolicy() return new ScannedReadOnlyPolicy(MarkedNodes); } + public TypeMapManager GetTypeMapManager() + { + return new ScannedTypeMapManager(_factory); + } + private sealed class ScannedVTableProvider : VTableSliceProvider { private readonly Dictionary _vtableSlices = new Dictionary(); @@ -983,5 +989,49 @@ public override bool IsReadOnly(FieldDesc field) return !_writtenFields.Contains(field); } } + + private sealed class ScannedTypeMapManager : TypeMapManager + { + private ImmutableArray _externalTypeMapNodes; + private ImmutableArray _proxyTypeMapNodes; + + public ScannedTypeMapManager(NodeFactory factory) + { + ImmutableArray.Builder externalTypeMapNodes = ImmutableArray.CreateBuilder(); + ImmutableArray.Builder proxyTypeMapNodes = ImmutableArray.CreateBuilder(); + foreach (var externalTypeMapNode in factory.TypeMapManager.GetExternalTypeMaps()) + { + externalTypeMapNodes.Add(externalTypeMapNode.ToAnalysisBasedNode(factory)); + } + + foreach (var proxyTypeMapNode in factory.TypeMapManager.GetProxyTypeMaps()) + { + proxyTypeMapNodes.Add(proxyTypeMapNode.ToAnalysisBasedNode(factory)); + } + + _externalTypeMapNodes = externalTypeMapNodes.ToImmutable(); + _proxyTypeMapNodes = proxyTypeMapNodes.ToImmutable(); + } + + protected override bool IsEmpty => _externalTypeMapNodes.Length == 0 && _proxyTypeMapNodes.Length == 0; + + public override void AddCompilationRoots(IRootingServiceProvider rootProvider) + { + const string reason = "Used Type Map Group"; + + foreach (IExternalTypeMapNode externalTypeMap in _externalTypeMapNodes) + { + rootProvider.AddCompilationRoot(externalTypeMap, reason); + } + + foreach (IProxyTypeMapNode proxyTypeMap in _proxyTypeMapNodes) + { + rootProvider.AddCompilationRoot(proxyTypeMap, reason); + } + } + + internal override IEnumerable GetExternalTypeMaps() => _externalTypeMapNodes; + internal override IEnumerable GetProxyTypeMaps() => _proxyTypeMapNodes; + } } } diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ILScannerBuilder.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ILScannerBuilder.cs index 887c90703daf7e..d231500c89f13b 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ILScannerBuilder.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ILScannerBuilder.cs @@ -25,6 +25,7 @@ public sealed class ILScannerBuilder private IEnumerable _compilationRoots = Array.Empty(); private MetadataManager _metadataManager; private InteropStubManager _interopStubManager = new EmptyInteropStubManager(); + private TypeMapManager _typeMapManager = new UsageBasedTypeMapManager(TypeMapMetadata.Empty); private int _parallelism = -1; internal ILScannerBuilder(CompilerTypeSystemContext context, CompilationModuleGroup compilationGroup, NameMangler mangler, ILProvider ilProvider, PreinitializationManager preinitializationManager) @@ -61,6 +62,12 @@ public ILScannerBuilder UseInteropStubManager(InteropStubManager interopStubMana return this; } + public ILScannerBuilder UseTypeMapManager(TypeMapManager typeMapManager) + { + _typeMapManager = typeMapManager; + return this; + } + public ILScannerBuilder UseParallelism(int parallelism) { _parallelism = parallelism; @@ -75,10 +82,10 @@ public ILScannerBuilder UseLogger(Logger logger) public IILScanner ToILScanner() { - var nodeFactory = new ILScanNodeFactory(_context, _compilationGroup, _metadataManager, _interopStubManager, _nameMangler, _preinitializationManager); + var nodeFactory = new ILScanNodeFactory(_context, _compilationGroup, _metadataManager, _interopStubManager, _nameMangler, _preinitializationManager, _typeMapManager); DependencyAnalyzerBase graph = _dependencyTrackingLevel.CreateDependencyGraph(nodeFactory); - return new ILScanner(graph, nodeFactory, _compilationRoots, _ilProvider, new NullDebugInformationProvider(), _logger, _parallelism); + return new ILScanner(graph, nodeFactory, [.._compilationRoots, _typeMapManager], _ilProvider, new NullDebugInformationProvider(), _logger, _parallelism); } } } diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/TypeMapManager.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/TypeMapManager.cs new file mode 100644 index 00000000000000..b4392aead3d712 --- /dev/null +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/TypeMapManager.cs @@ -0,0 +1,68 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Diagnostics; +using System.Reflection.Metadata; +using ILCompiler.DependencyAnalysis; +using ILCompiler.DependencyAnalysisFramework; +using Internal.IL; +using Internal.IL.Stubs; +using Internal.TypeSystem; +using ReflectionMapBlob = Internal.Runtime.ReflectionMapBlob; + +namespace ILCompiler +{ + + /// + /// This class is responsible for managing emitted data for type maps. + /// + public abstract class TypeMapManager : ICompilationRootProvider + { + public enum TypeMapAttributeKind + { + None, + TypeMapAssemblyTarget, + TypeMap, + TypeMapAssociation + } + + public static TypeMapAttributeKind LookupTypeMapType(TypeDesc attrType) + { + TypeDesc typeDef = attrType.GetTypeDefinition(); + return typeDef switch + { + MetadataType { Namespace: "System.Runtime.InteropServices", Name: "TypeMapAssemblyTargetAttribute`1", Instantiation.Length: 1 } => TypeMapAttributeKind.TypeMapAssemblyTarget, + MetadataType { Namespace: "System.Runtime.InteropServices", Name: "TypeMapAttribute`1", Instantiation.Length: 1 } => TypeMapAttributeKind.TypeMap, + MetadataType { Namespace: "System.Runtime.InteropServices", Name: "TypeMapAssociationAttribute`1", Instantiation.Length: 1 } => TypeMapAttributeKind.TypeMapAssociation, + _ => TypeMapAttributeKind.None, + }; + } + + public virtual void AttachToDependencyGraph(DependencyAnalyzerBase graph) + { + } + + internal abstract IEnumerable GetExternalTypeMaps(); + + internal abstract IEnumerable GetProxyTypeMaps(); + + public abstract void AddCompilationRoots(IRootingServiceProvider rootProvider); + + protected abstract bool IsEmpty { get; } + + public void AddToReadyToRunHeader(ReadyToRunHeaderNode header, NodeFactory nodeFactory, ExternalReferencesTableNode commonFixupsTableNode) + { + if (IsEmpty) + { + return; // No type maps to emit + } + + header.Add(MetadataManager.BlobIdToReadyToRunSection(ReflectionMapBlob.ExternalTypeMap), new ExternalTypeMapObjectNode(commonFixupsTableNode)); + header.Add(MetadataManager.BlobIdToReadyToRunSection(ReflectionMapBlob.ProxyTypeMap), new ProxyTypeMapObjectNode(commonFixupsTableNode)); + } + } +} diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/TypeMapMetadata.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/TypeMapMetadata.cs new file mode 100644 index 00000000000000..4d2c597ee2447e --- /dev/null +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/TypeMapMetadata.cs @@ -0,0 +1,288 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Reflection.Metadata; +using ILCompiler.DependencyAnalysis; +using Internal.IL; +using Internal.IL.Stubs; +using Internal.TypeSystem; +using Internal.TypeSystem.Ecma; +using static ILCompiler.TypeMapManager; +using static ILCompiler.UsageBasedTypeMapManager; + +namespace ILCompiler +{ + public sealed class TypeMapMetadata + { + internal sealed class Map + { + private sealed class ThrowingMethodStub : ILStubMethod + { + private readonly TypeDesc _typeMapGroup; + + public ThrowingMethodStub(TypeDesc owningType, TypeDesc typeMapGroup, bool externalTypeMap, TypeSystemException ex) + { + OwningType = owningType; + _typeMapGroup = typeMapGroup; + Name = $"InvalidTypeMapStub_{_typeMapGroup}_{(externalTypeMap ? "External" : "Proxy")}"; + Exception = ex; + } + + public TypeSystemException Exception { get; } + public override string Name { get; } + public override MethodIL EmitIL() + { + return TypeSystemThrowingILEmitter.EmitIL(this, Exception); + } + + protected override int CompareToImpl(MethodDesc other, TypeSystemComparer comparer) + { + return Name.CompareTo(other.Name, StringComparison.Ordinal); + } + + public override bool IsPInvoke => false; + + public override string DiagnosticName => Name; + + protected override int ClassCode => 1744789196; + + public override TypeDesc OwningType { get; } + + public override MethodSignature Signature => new MethodSignature(MethodSignatureFlags.Static, 0, Context.GetWellKnownType(WellKnownType.Void), []); + + public override TypeSystemContext Context => OwningType.Context; + } + + private readonly Dictionary _associatedTypeMap = []; + private readonly Dictionary _externalTypeMap = []; + private ThrowingMethodStub _externalTypeMapExceptionStub; + private ThrowingMethodStub _associatedTypeMapExceptionStub; + + public Map(TypeDesc typeMapGroup) + { + TypeMapGroup = typeMapGroup; + } + + public TypeDesc TypeMapGroup { get; } + + public void AddAssociatedTypeMapEntry(TypeDesc type, TypeDesc associatedType) + { + if (!_associatedTypeMap.TryAdd(type, associatedType)) + { + ThrowHelper.ThrowBadImageFormatException(); + } + } + public void AddExternalTypeMapEntry(string typeName, TypeDesc type, TypeDesc trimmingTarget) + { + if (!_externalTypeMap.TryAdd(typeName, (type, trimmingTarget))) + { + ThrowHelper.ThrowBadImageFormatException(); + } + } + + public void SetExternalTypeMapException(ModuleDesc stubModule, TypeSystemException exception) + { + if (_externalTypeMapExceptionStub?.Exception is TypeSystemException.FileNotFoundException) + { + // FileNotFound exception takes precedence. + return; + } + _externalTypeMapExceptionStub ??= new ThrowingMethodStub(stubModule.GetGlobalModuleType(), TypeMapGroup, externalTypeMap: true, exception); + } + + public void SetAssociatedTypeMapException(ModuleDesc stubModule, TypeSystemException exception) + { + if (_associatedTypeMapExceptionStub?.Exception is TypeSystemException.FileNotFoundException) + { + // FileNotFound exception takes precedence. + return; + } + _associatedTypeMapExceptionStub ??= new ThrowingMethodStub(stubModule.GetGlobalModuleType(), TypeMapGroup, externalTypeMap: false, exception); + } + + public IExternalTypeMapNode GetExternalTypeMapNode() + { + if (_externalTypeMapExceptionStub is not null) + { + return new InvalidExternalTypeMapNode(TypeMapGroup, _externalTypeMapExceptionStub); + } + return new ExternalTypeMapNode(TypeMapGroup, _externalTypeMap); + } + + public IProxyTypeMapNode GetProxyTypeMapNode() + { + if (_associatedTypeMapExceptionStub is not null) + { + return new InvalidProxyTypeMapNode(TypeMapGroup, _associatedTypeMapExceptionStub); + } + return new ProxyTypeMapNode(TypeMapGroup, _associatedTypeMap); + } + } + + public static readonly TypeMapMetadata Empty = new TypeMapMetadata(new Dictionary(), "No type maps"); + + private readonly IReadOnlyDictionary _states; + + private TypeMapMetadata(IReadOnlyDictionary states, string diagnosticName) + { + _states = states; + DiagnosticName = diagnosticName; + } + + internal Map this[TypeDesc typeMapGroup] => _states[typeMapGroup]; + + public bool IsEmpty => _states.Count == 0; + + internal IEnumerable> Maps => _states; + + public string DiagnosticName { get; } + + public static TypeMapMetadata CreateFromAssembly(EcmaAssembly assembly, CompilerTypeSystemContext typeSystemContext) + { + Dictionary typeMapStates = []; + HashSet scannedAssemblies = []; + + Queue assembliesToScan = new Queue(); + assembliesToScan.Enqueue(assembly); + + while (assembliesToScan.Count > 0) + { + EcmaAssembly currentAssembly = assembliesToScan.Dequeue(); + if (scannedAssemblies.Contains(currentAssembly)) + continue; + + scannedAssemblies.Add(currentAssembly); + + foreach (CustomAttributeHandle attrHandle in currentAssembly.MetadataReader.GetCustomAttributes(EntityHandle.AssemblyDefinition)) + { + CustomAttribute attr = currentAssembly.MetadataReader.GetCustomAttribute(attrHandle); + + if (!MetadataExtensions.GetAttributeTypeAndConstructor(currentAssembly.MetadataReader, attrHandle, out EntityHandle attributeType, out _)) + { + continue; + } + + TypeDesc type = (TypeDesc)currentAssembly.GetObject(attributeType); + + TypeMapAttributeKind attrKind = LookupTypeMapType(type); + + if (attrKind == TypeMapAttributeKind.None) + { + // Not a type map attribute, skip it + continue; + } + + CustomAttributeValue attrValue = attr.DecodeValue(new CustomAttributeTypeProvider(currentAssembly)); + + TypeDesc typeMapGroup = type.Instantiation[0]; + + try + { + switch (attrKind) + { + case TypeMapAttributeKind.TypeMapAssemblyTarget: + ProcessTypeMapAssemblyTargetAttribute(attrValue); + break; + + case TypeMapAttributeKind.TypeMap: + ProcessTypeMapAttribute(attrValue, typeMapGroup); + break; + + case TypeMapAttributeKind.TypeMapAssociation: + ProcessTypeMapAssociationAttribute(attrValue, typeMapGroup); + break; + + default: + Debug.Fail($"Unexpected TypeMapAttributeKind: {attrKind}"); + break; + } + } + catch (TypeSystemException ex) + { + if (!typeMapStates.TryGetValue(typeMapGroup, out Map value)) + { + value = new Map(typeMapGroup); + typeMapStates[typeMapGroup] = value; + } + + if (attrKind is TypeMapAttributeKind.TypeMapAssemblyTarget or TypeMapAttributeKind.TypeMap) + { + value.SetExternalTypeMapException(typeSystemContext.GeneratedAssembly, ex); + } + + if (attrKind is TypeMapAttributeKind.TypeMapAssemblyTarget or TypeMapAttributeKind.TypeMapAssociation) + { + value.SetAssociatedTypeMapException(typeSystemContext.GeneratedAssembly, ex); + } + } + } + + void ProcessTypeMapAssemblyTargetAttribute(CustomAttributeValue attrValue) + { + if (attrValue.FixedArguments is not [{ Value: string assemblyName }]) + { + ThrowHelper.ThrowBadImageFormatException(); + return; + } + + EcmaAssembly targetAssembly = (EcmaAssembly)assembly.Context.ResolveAssembly(AssemblyNameInfo.Parse(assemblyName), throwIfNotFound: true); + + assembliesToScan.Enqueue(targetAssembly); + } + + void ProcessTypeMapAttribute(CustomAttributeValue attrValue, TypeDesc typeMapGroup) + { + switch (attrValue.FixedArguments) + { + case [{ Value: string typeName }, { Value: TypeDesc targetType }]: + { + if (!typeMapStates.TryGetValue(typeMapGroup, out Map typeMapState)) + { + typeMapStates[typeMapGroup] = typeMapState = new Map(typeMapGroup); + } + typeMapState.AddExternalTypeMapEntry(typeName, targetType, targetType); + break; + } + + case [{ Value: string typeName }, { Value: TypeDesc targetType }, { Value: TypeDesc trimTargetType }]: + { + if (!typeMapStates.TryGetValue(typeMapGroup, out Map typeMapState)) + { + typeMapStates[typeMapGroup] = typeMapState = new Map(typeMapGroup); + } + typeMapState.AddExternalTypeMapEntry(typeName, targetType, trimTargetType); + break; + } + + default: + ThrowHelper.ThrowBadImageFormatException(); + return; + } + } + + void ProcessTypeMapAssociationAttribute(CustomAttributeValue attrValue, TypeDesc typeMapGroup) + { + // 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 }]) + { + ThrowHelper.ThrowBadImageFormatException(); + return; + } + + if (!typeMapStates.TryGetValue(typeMapGroup, out Map typeMapState)) + { + typeMapStates[typeMapGroup] = typeMapState = new Map(typeMapGroup); + } + + typeMapState.AddAssociatedTypeMapEntry(type, associatedType); + } + } + + return new TypeMapMetadata(typeMapStates, $"Type maps rooted at {assembly}"); + } + } +} diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/UsageBasedTypeMapManager.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/UsageBasedTypeMapManager.cs new file mode 100644 index 00000000000000..54be1c847d7e6f --- /dev/null +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/UsageBasedTypeMapManager.cs @@ -0,0 +1,137 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.Text; +using ILCompiler.DependencyAnalysis; +using ILCompiler.DependencyAnalysisFramework; +using Internal.IL; +using Internal.IL.Stubs; +using Internal.TypeSystem; +using Internal.TypeSystem.Ecma; + +namespace ILCompiler +{ + public sealed class UsageBasedTypeMapManager : TypeMapManager + { + private sealed class AllTypeMapsNode(TypeMapMetadata typeMapState) : DependencyNodeCore + { + public override bool InterestingForDynamicDependencyAnalysis => false; + + public override bool HasDynamicDependencies => false; + + public override bool HasConditionalStaticDependencies => true; + + public override bool StaticDependenciesAreComputed => true; + + public override IEnumerable GetConditionalStaticDependencies(NodeFactory context) + { + List entries = []; + foreach ((TypeDesc typeMapGroup, TypeMapMetadata.Map typeMap) in typeMapState.Maps) + { + entries.Add(new CombinedDependencyListEntry(typeMap.GetExternalTypeMapNode(), context.ExternalTypeMapRequest(typeMapGroup), "ExternalTypeMap")); + entries.Add(new CombinedDependencyListEntry(typeMap.GetProxyTypeMapNode(), context.ProxyTypeMapRequest(typeMapGroup), "ProxyTypeMap")); + } + + return entries; + } + + public override IEnumerable GetStaticDependencies(NodeFactory context) => Array.Empty(); + public override IEnumerable SearchDynamicDependencies(List> markedNodes, int firstNode, NodeFactory context) => Array.Empty(); + protected override string GetName(NodeFactory context) => $"Type maps root node: {typeMapState.DiagnosticName}"; + } + + private readonly HashSet _requestedExternalTypeMaps = []; + private readonly HashSet _requestedProxyTypeMaps = []; + private readonly SortedSet _externalTypeMaps = new SortedSet(CompilerComparer.Instance); + private readonly SortedSet _proxyTypeMaps = new SortedSet(CompilerComparer.Instance); + + private readonly TypeMapMetadata _typeMaps; + + public UsageBasedTypeMapManager(TypeMapMetadata state) + { + _typeMaps = state; + } + + protected override bool IsEmpty => _typeMaps.IsEmpty; + + public override void AttachToDependencyGraph(DependencyAnalyzerBase graph) + { + base.AttachToDependencyGraph(graph); + + graph.NewMarkedNode += Graph_NewMarkedNode; + } + + private void Graph_NewMarkedNode(DependencyNodeCore obj) + { + if (obj is IExternalTypeMapNode externalTypeMapNode) + { + _externalTypeMaps.Add(externalTypeMapNode); + } + + if (obj is IProxyTypeMapNode proxyTypeMapNode) + { + _proxyTypeMaps.Add(proxyTypeMapNode); + } + + if (obj is ExternalTypeMapRequestNode externalTypeMapRequestNode) + { + _requestedExternalTypeMaps.Add(externalTypeMapRequestNode.TypeMapGroup); + } + + if (obj is ProxyTypeMapRequestNode proxyTypeMapRequestNode) + { + _requestedProxyTypeMaps.Add(proxyTypeMapRequestNode.TypeMapGroup); + } + } + + internal override IEnumerable GetExternalTypeMaps() + { + List typeMaps = [.._externalTypeMaps]; + SortedSet generatedMaps = new(TypeSystemComparer.Instance); + foreach (var generatedMap in typeMaps) + { + generatedMaps.Add(generatedMap.TypeMapGroup); + } + + SortedSet emptyMapsToGenerate = new SortedSet(_requestedExternalTypeMaps, TypeSystemComparer.Instance); + emptyMapsToGenerate.ExceptWith(generatedMaps); + + foreach (var emptyMap in emptyMapsToGenerate) + { + typeMaps.Add(new ExternalTypeMapNode(emptyMap, [])); + } + return typeMaps; + } + + internal override IEnumerable GetProxyTypeMaps() + { + List typeMaps = [.. _proxyTypeMaps]; + SortedSet generatedMaps = new(TypeSystemComparer.Instance); + foreach (var generatedMap in typeMaps) + { + generatedMaps.Add(generatedMap.TypeMapGroup); + } + + SortedSet emptyMapsToGenerate = new SortedSet(_requestedProxyTypeMaps, TypeSystemComparer.Instance); + emptyMapsToGenerate.ExceptWith(generatedMaps); + + foreach (var emptyMap in emptyMapsToGenerate) + { + typeMaps.Add(new ProxyTypeMapNode(emptyMap, [])); + } + return typeMaps; + } + + public override void AddCompilationRoots(IRootingServiceProvider rootProvider) + { + if (_typeMaps.IsEmpty) + { + return; // No type maps to process + } + + rootProvider.AddCompilationRoot(new AllTypeMapsNode(_typeMaps), "TypeMapManager"); + } + } +} diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/ILCompiler.Compiler.csproj b/src/coreclr/tools/aot/ILCompiler.Compiler/ILCompiler.Compiler.csproj index 7de58a994435fb..8f0bb038d21fe1 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/ILCompiler.Compiler.csproj +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/ILCompiler.Compiler.csproj @@ -346,6 +346,8 @@ + + @@ -407,6 +409,12 @@ + + + + + + @@ -419,6 +427,7 @@ + @@ -439,6 +448,7 @@ + @@ -509,6 +519,7 @@ + @@ -523,6 +534,7 @@ + @@ -623,6 +635,7 @@ + @@ -645,9 +658,11 @@ + + diff --git a/src/coreclr/tools/aot/ILCompiler.RyuJit/Compiler/DependencyAnalysis/RyuJitNodeFactory.cs b/src/coreclr/tools/aot/ILCompiler.RyuJit/Compiler/DependencyAnalysis/RyuJitNodeFactory.cs index fa328d268a9a5e..1e75766b8f5ed3 100644 --- a/src/coreclr/tools/aot/ILCompiler.RyuJit/Compiler/DependencyAnalysis/RyuJitNodeFactory.cs +++ b/src/coreclr/tools/aot/ILCompiler.RyuJit/Compiler/DependencyAnalysis/RyuJitNodeFactory.cs @@ -11,8 +11,8 @@ public sealed class RyuJitNodeFactory : NodeFactory { public RyuJitNodeFactory(CompilerTypeSystemContext context, CompilationModuleGroup compilationModuleGroup, MetadataManager metadataManager, InteropStubManager interopStubManager, NameMangler nameMangler, VTableSliceProvider vtableSliceProvider, DictionaryLayoutProvider dictionaryLayoutProvider, InlinedThreadStatics inlinedThreadStatics, PreinitializationManager preinitializationManager, - DevirtualizationManager devirtualizationManager, ObjectDataInterner dataInterner) - : base(context, compilationModuleGroup, metadataManager, interopStubManager, nameMangler, new LazyGenericsDisabledPolicy(), vtableSliceProvider, dictionaryLayoutProvider, inlinedThreadStatics, new ExternSymbolsImportedNodeProvider(), preinitializationManager, devirtualizationManager, dataInterner) + DevirtualizationManager devirtualizationManager, ObjectDataInterner dataInterner, TypeMapManager typeMapManager) + : base(context, compilationModuleGroup, metadataManager, interopStubManager, nameMangler, new LazyGenericsDisabledPolicy(), vtableSliceProvider, dictionaryLayoutProvider, inlinedThreadStatics, new ExternSymbolsImportedNodeProvider(), preinitializationManager, devirtualizationManager, dataInterner, typeMapManager) { } diff --git a/src/coreclr/tools/aot/ILCompiler.RyuJit/Compiler/RyuJitCompilationBuilder.cs b/src/coreclr/tools/aot/ILCompiler.RyuJit/Compiler/RyuJitCompilationBuilder.cs index 7952b51e26533b..b1291ac837dce8 100644 --- a/src/coreclr/tools/aot/ILCompiler.RyuJit/Compiler/RyuJitCompilationBuilder.cs +++ b/src/coreclr/tools/aot/ILCompiler.RyuJit/Compiler/RyuJitCompilationBuilder.cs @@ -131,13 +131,13 @@ public override ICompilation ToCompilation() ObjectDataInterner interner = _methodBodyFolding ? new ObjectDataInterner() : ObjectDataInterner.Null; - var factory = new RyuJitNodeFactory(_context, _compilationGroup, _metadataManager, _interopStubManager, _nameMangler, _vtableSliceProvider, _dictionaryLayoutProvider, _inlinedThreadStatics, GetPreinitializationManager(), _devirtualizationManager, interner); + var factory = new RyuJitNodeFactory(_context, _compilationGroup, _metadataManager, _interopStubManager, _nameMangler, _vtableSliceProvider, _dictionaryLayoutProvider, _inlinedThreadStatics, GetPreinitializationManager(), _devirtualizationManager, interner, _typeMapManager); JitConfigProvider.Initialize(_context.Target, jitFlagBuilder.ToArray(), _ryujitOptions, _jitPath); DependencyAnalyzerBase graph = CreateDependencyGraph(factory, new ObjectNode.ObjectNodeComparer(CompilerComparer.Instance)); return new RyuJitCompilation(graph, factory, - _compilationRoots, + [.._compilationRoots, _typeMapManager], _ilProvider, _debugInformationProvider, _logger, diff --git a/src/coreclr/tools/aot/ILCompiler.Trimming.Tests/TestCasesRunner/TrimmingDriver.cs b/src/coreclr/tools/aot/ILCompiler.Trimming.Tests/TestCasesRunner/TrimmingDriver.cs index f87cbf739f11b6..a97f9638c5f8f0 100644 --- a/src/coreclr/tools/aot/ILCompiler.Trimming.Tests/TestCasesRunner/TrimmingDriver.cs +++ b/src/coreclr/tools/aot/ILCompiler.Trimming.Tests/TestCasesRunner/TrimmingDriver.cs @@ -117,28 +117,34 @@ public ILScanResults Trim (ILCompilerOptions options, TrimmingCustomizations? cu CompilerGeneratedState compilerGeneratedState = new CompilerGeneratedState (ilProvider, logger); - UsageBasedMetadataManager metadataManager = new UsageBasedMetadataManager ( + UsageBasedMetadataManager metadataManager = new UsageBasedMetadataManager( compilationGroup, typeSystemContext, - new NoMetadataBlockingPolicy (), - new ManifestResourceBlockingPolicy (logger, options.FeatureSwitches, new Dictionary>()), + new NoMetadataBlockingPolicy(), + new ManifestResourceBlockingPolicy(logger, options.FeatureSwitches, new Dictionary>()), logFile: null, - new NoStackTraceEmissionPolicy (), - new DefaultDynamicInvokeThunkGenerationPolicy (), - new FlowAnnotations (logger, ilProvider, compilerGeneratedState), - UsageBasedMetadataGenerationOptions.ReflectionILScanning, + stackTracePolicy: new NoStackTraceEmissionPolicy(), + invokeThunkGenerationPolicy: new DefaultDynamicInvokeThunkGenerationPolicy(), + flowAnnotations: new FlowAnnotations(logger, ilProvider, compilerGeneratedState), + generationOptions: UsageBasedMetadataGenerationOptions.ReflectionILScanning, options: default, - logger, - options.FeatureSwitches, - Array.Empty (), - options.AdditionalRootAssemblies.ToArray (), - options.TrimAssemblies.ToArray (), - Array.Empty ()); + logger: logger, + featureSwitchValues: options.FeatureSwitches, + rootEntireAssembliesModules: Array.Empty(), + additionalRootedAssemblies: options.AdditionalRootAssemblies.ToArray(), + trimmedAssemblies: options.TrimAssemblies.ToArray(), + satelliteAssemblyFilePaths: Array.Empty()); PInvokeILEmitterConfiguration pinvokePolicy = new ILCompilerTestPInvokePolicy (); InteropStateManager interopStateManager = new InteropStateManager (typeSystemContext.GeneratedAssembly); InteropStubManager interopStubManager = new UsageBasedInteropStubManager (interopStateManager, pinvokePolicy, logger); + TypeMapManager typeMapManager = new UsageBasedTypeMapManager (TypeMapMetadata.Empty); + if (entrypointModule is { Assembly: EcmaAssembly entryAssembly }) + { + typeMapManager = new UsageBasedTypeMapManager (TypeMapMetadata.CreateFromAssembly(entryAssembly, typeSystemContext)); + } + CompilationBuilder builder = new RyuJitCompilationBuilder (typeSystemContext, compilationGroup) .UseILProvider (ilProvider) .UseCompilationUnitPrefix(""); @@ -148,6 +154,7 @@ public ILScanResults Trim (ILCompilerOptions options, TrimmingCustomizations? cu .UseMetadataManager (metadataManager) .UseParallelism (System.Diagnostics.Debugger.IsAttached ? 1 : -1) .UseInteropStubManager (interopStubManager) + .UseTypeMapManager (typeMapManager) .ToILScanner (); return scanner.Scan (); diff --git a/src/coreclr/tools/aot/ILCompiler/Program.cs b/src/coreclr/tools/aot/ILCompiler/Program.cs index 2f69c14df2e326..1e301684b7721b 100644 --- a/src/coreclr/tools/aot/ILCompiler/Program.cs +++ b/src/coreclr/tools/aot/ILCompiler/Program.cs @@ -192,12 +192,17 @@ public int Run() CompilationModuleGroup compilationGroup; List compilationRoots = new List(); + TypeMapManager typeMapManager = new UsageBasedTypeMapManager(TypeMapMetadata.Empty); bool multiFile = Get(_command.MultiFile); if (singleMethod != null) { // Compiling just a single method compilationGroup = new SingleMethodCompilationModuleGroup(singleMethod); compilationRoots.Add(new SingleMethodRootProvider(singleMethod)); + if (singleMethod.OwningType is MetadataType { Module.Assembly: EcmaAssembly assembly }) + { + typeMapManager = new UsageBasedTypeMapManager(TypeMapMetadata.CreateFromAssembly(assembly, typeSystemContext)); + } } else { @@ -307,6 +312,11 @@ public int Run() throw new CommandLineException($"'{linkTrimFilePath}' doesn't exist"); compilationRoots.Add(new ILCompiler.DependencyAnalysis.TrimmingDescriptorNode(linkTrimFilePath)); } + + if (entrypointModule is { Assembly: EcmaAssembly entryAssembly }) + { + typeMapManager = new UsageBasedTypeMapManager(TypeMapMetadata.CreateFromAssembly(entryAssembly, typeSystemContext)); + } } // Root whatever assemblies were specified on the command line @@ -467,7 +477,8 @@ public int Run() var preinitManager = new PreinitializationManager(typeSystemContext, compilationGroup, ilProvider, preinitPolicy, new StaticReadOnlyFieldPolicy(), flowAnnotations); builder .UseILProvider(ilProvider) - .UsePreinitializationManager(preinitManager); + .UsePreinitializationManager(preinitManager) + .UseTypeMapManager(typeMapManager); #if DEBUG List scannerConstructedTypes = null; @@ -490,6 +501,7 @@ void RunScanner() .UseMetadataManager(metadataManager) .UseParallelism(parallelism) .UseInteropStubManager(interopStubManager) + .UseTypeMapManager(typeMapManager) .UseLogger(logger); string scanDgmlLogFileName = Get(_command.ScanDgmlLogFileName); @@ -513,6 +525,8 @@ void RunScanner() metadataManager = ((UsageBasedMetadataManager)metadataManager).ToAnalysisBasedMetadataManager(); + builder.UseTypeMapManager(scanResults.GetTypeMapManager()); + interopStubManager = scanResults.GetInteropStubManager(interopStateManager, pinvokePolicy); ilProvider = new SubstitutedILProvider(unsubstitutedILProvider, substitutionProvider, devirtualizationManager, metadataManager); diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/TypeMapping.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/TypeMapping.cs index ff6b5b542a2f99..c652ef9887fcdc 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/TypeMapping.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/TypeMapping.cs @@ -23,9 +23,7 @@ public static class TypeMapping [RequiresUnreferencedCode("Interop types may be removed by trimming")] public static IReadOnlyDictionary GetOrCreateExternalTypeMapping() { -#if NATIVEAOT - throw new NotImplementedException(); -#elif MONO +#if MONO throw new NotSupportedException(); #else return TypeMapLazyDictionary.CreateExternalTypeMap((RuntimeType)typeof(TTypeMapGroup)); @@ -43,9 +41,7 @@ public static IReadOnlyDictionary GetOrCreateExternalTypeMapping GetOrCreateProxyTypeMapping() { -#if NATIVEAOT - throw new NotImplementedException(); -#elif MONO +#if MONO throw new NotSupportedException(); #else return TypeMapLazyDictionary.CreateProxyTypeMap((RuntimeType)typeof(TTypeMapGroup)); diff --git a/src/libraries/System.Runtime.InteropServices/tests/TrimmingTests/System.Runtime.InteropServices.TrimmingTests.proj b/src/libraries/System.Runtime.InteropServices/tests/TrimmingTests/System.Runtime.InteropServices.TrimmingTests.proj index daf43dbb12a7b1..9aa8090bd00f56 100644 --- a/src/libraries/System.Runtime.InteropServices/tests/TrimmingTests/System.Runtime.InteropServices.TrimmingTests.proj +++ b/src/libraries/System.Runtime.InteropServices/tests/TrimmingTests/System.Runtime.InteropServices.TrimmingTests.proj @@ -18,10 +18,14 @@ win-x64;browser-wasm - + osx-x64;linux-x64;browser-wasm IlcTrimMetadata + + + IlcGenerateMstatFile;IlcGenerateDgmlFile + diff --git a/src/libraries/System.Runtime.InteropServices/tests/TrimmingTests/TypeMap.cs b/src/libraries/System.Runtime.InteropServices/tests/TrimmingTests/TypeMap.cs new file mode 100644 index 00000000000000..5dcf5e35009ecd --- /dev/null +++ b/src/libraries/System.Runtime.InteropServices/tests/TrimmingTests/TypeMap.cs @@ -0,0 +1,118 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +[assembly: TypeMap("TrimTargetIsTarget", typeof(TargetAndTrimTarget), typeof(TargetAndTrimTarget))] +[assembly: TypeMap("TrimTargetIsUnrelated", typeof(TargetType), typeof(TrimTarget))] +[assembly: TypeMap("TrimTargetIsUnreferenced", typeof(UnreferencedTargetType), typeof(UnreferencedTrimTarget))] +[assembly: TypeMapAssociation(typeof(SourceClass), typeof(ProxyType))] + +[assembly: TypeMap("UnusedName", typeof(UnusedTargetType), typeof(TrimTarget))] +[assembly: TypeMapAssociation(typeof(UnusedSourceClass), typeof(UnusedProxyType))] + +if (args.Length > 1 && args[0] == "instantiate") +{ + Console.WriteLine("This code path should never actually be called. It exists exclusively for the trimmer to see that types are used in a way that it can't fully analyze."); + // Execute some code here to ensure that our "trim target" types are seen as "possibly used". + object t = Activator.CreateInstance(Type.GetType(args[1])); + if (t is TargetAndTrimTarget) + { + Console.WriteLine("Type deriving from TargetAndTrimTarget instantiated."); + } + else if (t is TrimTarget) + { + Console.WriteLine("Type deriving from TrimTarget instantiated."); + } + + Console.WriteLine("Hash code of SourceClass instance: " + new SourceClass().GetHashCode()); + return -1; +} + +IReadOnlyDictionary usedTypeMap = TypeMapping.GetOrCreateExternalTypeMapping(); + +if (!usedTypeMap.TryGetValue("TrimTargetIsTarget", out Type targetAndTrimTargetType)) +{ + Console.WriteLine("TrimTargetIsTarget not found in used type map."); + return 1; +} + +if (targetAndTrimTargetType != GetTypeWithoutTrimAnalysis(nameof(TargetAndTrimTarget))) +{ + Console.WriteLine("TrimTargetIsTarget type does not match expected type."); + return 2; +} + +if (!usedTypeMap.TryGetValue("TrimTargetIsUnrelated", out Type targetType)) +{ + Console.WriteLine("TrimTargetIsUnrelated not found in used type map."); + return 3; +} + +if (targetType != GetTypeWithoutTrimAnalysis(nameof(TargetType))) +{ + Console.WriteLine("TrimTargetIsUnrelated type does not match expected type."); + return 4; +} + +if (GetTypeWithoutTrimAnalysis(nameof(TrimTarget)) is not null) +{ + Console.WriteLine("TrimTarget should not be preserved if the only place that would preserve it is a check that is optimized away."); + return 5; +} + +if (usedTypeMap.TryGetValue("TrimTargetIsUnreferenced", out _)) +{ + Console.WriteLine("TrimTargetIsUnreferenced should not be found in used type map."); + return 6; +} + +IReadOnlyDictionary usedProxyTypeMap = TypeMapping.GetOrCreateProxyTypeMapping(); +if (!usedProxyTypeMap.TryGetValue(typeof(SourceClass), out Type proxyType)) +{ + Console.WriteLine("SourceClass not found in used proxy type map."); + return 7; +} + +if (proxyType != GetTypeWithoutTrimAnalysis(nameof(ProxyType))) +{ + Console.WriteLine("SourceClass proxy type does not match expected type."); + return 8; +} + +if (GetTypeWithoutTrimAnalysis(nameof(UnusedTargetType)) is not null) +{ + Console.WriteLine("UnusedTargetType should not be preserved if the external type map is not used and it is not referenced otherwise even if the entry's trim target is kept."); + return 9; +} + +if (GetTypeWithoutTrimAnalysis(nameof(UnusedProxyType)) is not null) +{ + Console.WriteLine("UnusedProxyType should not be preserved if the proxy type map is not used and it is not referenced otherwise even if the entry's source type is kept."); + return 10; +} + +return 100; + +[MethodImpl(MethodImplOptions.NoInlining)] +static Type GetTypeWithoutTrimAnalysis(string typeName) +{ + return Type.GetType(typeName, throwOnError: false); +} + +class UsedTypeMap; +class TargetAndTrimTarget; +class TargetType; +class TrimTarget; +class UnreferencedTargetType; +class UnreferencedTrimTarget; +class SourceClass; +class ProxyType; + +class UnusedTypeMap; +class UnusedTargetType; +class UnusedSourceClass; +class UnusedProxyType; diff --git a/src/tests/Interop/TypeMap/TypeMapApp.cs b/src/tests/Interop/TypeMap/TypeMapApp.cs index 63e1b64342316e..5a355855d89df5 100644 --- a/src/tests/Interop/TypeMap/TypeMapApp.cs +++ b/src/tests/Interop/TypeMap/TypeMapApp.cs @@ -101,40 +101,38 @@ public static void Validate_GroupType_Types() { Console.WriteLine(nameof(Validate_GroupType_Types)); - ValidateExternalTypeMap(); - ValidateExternalTypeMap(); - ValidateExternalTypeMap(); - ValidateExternalTypeMap(); - ValidateExternalTypeMap>(); - ValidateExternalTypeMap>(); - ValidateExternalTypeMap.I1>(); - ValidateExternalTypeMap.I2>(); - ValidateExternalTypeMap.I1>(); - ValidateExternalTypeMap.I2>(); - - ValidateProxyTypeMap(); - ValidateProxyTypeMap(); - ValidateProxyTypeMap(); - ValidateProxyTypeMap(); - ValidateProxyTypeMap>(); - ValidateProxyTypeMap>(); - ValidateProxyTypeMap.I1>(); - ValidateProxyTypeMap.I2>(); - ValidateProxyTypeMap.I1>(); - ValidateProxyTypeMap.I2>(); + ValidateExternalTypeMap(TypeMapping.GetOrCreateExternalTypeMapping()); + ValidateExternalTypeMap(TypeMapping.GetOrCreateExternalTypeMapping()); + ValidateExternalTypeMap(TypeMapping.GetOrCreateExternalTypeMapping()); + ValidateExternalTypeMap(TypeMapping.GetOrCreateExternalTypeMapping()); + ValidateExternalTypeMap(TypeMapping.GetOrCreateExternalTypeMapping>()); + ValidateExternalTypeMap(TypeMapping.GetOrCreateExternalTypeMapping>()); + ValidateExternalTypeMap(TypeMapping.GetOrCreateExternalTypeMapping.I1>()); + ValidateExternalTypeMap(TypeMapping.GetOrCreateExternalTypeMapping.I2>()); + ValidateExternalTypeMap(TypeMapping.GetOrCreateExternalTypeMapping.I1>()); + ValidateExternalTypeMap(TypeMapping.GetOrCreateExternalTypeMapping.I2>()); + + ValidateProxyTypeMap(TypeMapping.GetOrCreateProxyTypeMapping()); + ValidateProxyTypeMap(TypeMapping.GetOrCreateProxyTypeMapping()); + ValidateProxyTypeMap(TypeMapping.GetOrCreateProxyTypeMapping()); + ValidateProxyTypeMap(TypeMapping.GetOrCreateProxyTypeMapping()); + ValidateProxyTypeMap(TypeMapping.GetOrCreateProxyTypeMapping>()); + ValidateProxyTypeMap(TypeMapping.GetOrCreateProxyTypeMapping>()); + ValidateProxyTypeMap(TypeMapping.GetOrCreateProxyTypeMapping.I1>()); + ValidateProxyTypeMap(TypeMapping.GetOrCreateProxyTypeMapping.I2>()); + ValidateProxyTypeMap(TypeMapping.GetOrCreateProxyTypeMapping.I1>()); + ValidateProxyTypeMap(TypeMapping.GetOrCreateProxyTypeMapping.I2>()); [MethodImpl(MethodImplOptions.NoInlining)] - static void ValidateExternalTypeMap() + static void ValidateExternalTypeMap(IReadOnlyDictionary map) { - IReadOnlyDictionary map = TypeMapping.GetOrCreateExternalTypeMapping(); Assert.Equal(typeof(string), map["1"]); Assert.False(map.TryGetValue("2", out Type? _)); } [MethodImpl(MethodImplOptions.NoInlining)] - static void ValidateProxyTypeMap() + static void ValidateProxyTypeMap(IReadOnlyDictionary map) { - IReadOnlyDictionary map = TypeMapping.GetOrCreateProxyTypeMapping(); Assert.Equal(typeof(string), map[typeof(object)]); Assert.False(map.TryGetValue(typeof(string), out Type? _)); } @@ -184,7 +182,7 @@ public static void Validate_ExternalTypeMapping_DuplicateTypeKey() { Console.WriteLine(nameof(Validate_ExternalTypeMapping_DuplicateTypeKey)); - Assert.Throws(() => TypeMapping.GetOrCreateExternalTypeMapping()); + AssertExtensions.ThrowsAny(() => TypeMapping.GetOrCreateExternalTypeMapping()); } [Fact] @@ -264,7 +262,7 @@ public static void Validate_EmptyOrInvalidMappings() { Console.WriteLine(nameof(Validate_EmptyOrInvalidMappings)); - Assert.Throws(() => TypeMapping.GetOrCreateExternalTypeMapping()); - Assert.Throws(() => TypeMapping.GetOrCreateProxyTypeMapping()); + AssertExtensions.ThrowsAny(() => TypeMapping.GetOrCreateExternalTypeMapping()); + AssertExtensions.ThrowsAny(() => TypeMapping.GetOrCreateProxyTypeMapping()); } -} \ No newline at end of file +} diff --git a/src/tests/Interop/TypeMap/TypeMapApp.csproj b/src/tests/Interop/TypeMap/TypeMapApp.csproj index b9d153fc8e8942..345b96c4fb75dc 100644 --- a/src/tests/Interop/TypeMap/TypeMapApp.csproj +++ b/src/tests/Interop/TypeMap/TypeMapApp.csproj @@ -4,8 +4,9 @@ true True true - true true + true + true diff --git a/src/tests/Interop/TypeMap/TypeMapLib1.csproj b/src/tests/Interop/TypeMap/TypeMapLib1.csproj index 7d08b32f779ad2..2193b71207b019 100644 --- a/src/tests/Interop/TypeMap/TypeMapLib1.csproj +++ b/src/tests/Interop/TypeMap/TypeMapLib1.csproj @@ -2,11 +2,9 @@ library true - true true - diff --git a/src/tests/Interop/TypeMap/TypeMapLib2.csproj b/src/tests/Interop/TypeMap/TypeMapLib2.csproj index 8b16e48913a27d..50629c2438f912 100644 --- a/src/tests/Interop/TypeMap/TypeMapLib2.csproj +++ b/src/tests/Interop/TypeMap/TypeMapLib2.csproj @@ -2,11 +2,9 @@ library true - true true - diff --git a/src/tests/Interop/TypeMap/TypeMapLib3.csproj b/src/tests/Interop/TypeMap/TypeMapLib3.csproj index bce24f7a9ea85d..1df043651476d5 100644 --- a/src/tests/Interop/TypeMap/TypeMapLib3.csproj +++ b/src/tests/Interop/TypeMap/TypeMapLib3.csproj @@ -2,7 +2,6 @@ library true - true true @@ -12,4 +11,3 @@ - diff --git a/src/tests/Interop/TypeMap/TypeMapLib4.csproj b/src/tests/Interop/TypeMap/TypeMapLib4.csproj index 225c462d4d92e4..ba85519a4a3726 100644 --- a/src/tests/Interop/TypeMap/TypeMapLib4.csproj +++ b/src/tests/Interop/TypeMap/TypeMapLib4.csproj @@ -2,7 +2,6 @@ library true - true true @@ -12,4 +11,3 @@ - diff --git a/src/tools/illink/src/ILLink.RoslynAnalyzer/DynamicallyAccessedMembersAnalyzer.cs b/src/tools/illink/src/ILLink.RoslynAnalyzer/DynamicallyAccessedMembersAnalyzer.cs index 1f31934d9b898b..9d8e2d8671c118 100644 --- a/src/tools/illink/src/ILLink.RoslynAnalyzer/DynamicallyAccessedMembersAnalyzer.cs +++ b/src/tools/illink/src/ILLink.RoslynAnalyzer/DynamicallyAccessedMembersAnalyzer.cs @@ -59,6 +59,7 @@ public static ImmutableArray GetSupportedDiagnostics () diagDescriptorsArrayBuilder.Add (DiagnosticDescriptors.GetDiagnosticDescriptor (DiagnosticId.ParametersOfAssemblyCreateInstanceCannotBeAnalyzed)); diagDescriptorsArrayBuilder.Add (DiagnosticDescriptors.GetDiagnosticDescriptor (DiagnosticId.ReturnValueDoesNotMatchFeatureGuards)); diagDescriptorsArrayBuilder.Add (DiagnosticDescriptors.GetDiagnosticDescriptor (DiagnosticId.InvalidFeatureGuard)); + diagDescriptorsArrayBuilder.Add (DiagnosticDescriptors.GetDiagnosticDescriptor (DiagnosticId.TypeMapGroupTypeCannotBeStaticallyDetermined)); foreach (var requiresAnalyzer in RequiresAnalyzers.Value) { foreach (var diagnosticDescriptor in requiresAnalyzer.SupportedDiagnostics) diff --git a/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/HandleCallAction.cs b/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/HandleCallAction.cs index 620e9fa582bb17..5390d309bec51d 100644 --- a/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/HandleCallAction.cs +++ b/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/HandleCallAction.cs @@ -44,6 +44,45 @@ public HandleCallAction ( _multiValueLattice = multiValueLattice; } + sealed class HasGenericTypeSymbolsVisitor : SymbolVisitor + { + internal static readonly HasGenericTypeSymbolsVisitor Instance = new (); + + public override bool VisitArrayType (IArrayTypeSymbol symbol) => Visit(symbol.ElementType); + + public override bool VisitPointerType (IPointerTypeSymbol symbol) => Visit(symbol.PointedAtType); + + public override bool VisitTypeParameter (ITypeParameterSymbol symbol) => true; + + public override bool VisitFunctionPointerType (IFunctionPointerTypeSymbol symbol) + { + IMethodSymbol signature = symbol.Signature; + + if (Visit (signature.ReturnType)) { + return true; + } + + foreach (var param in signature.Parameters) { + if (Visit(param.Type)) { + return true; + } + } + + return false; + } + + public override bool VisitNamedType (INamedTypeSymbol symbol) + { + foreach (var arg in symbol.TypeArguments) { + if (Visit (arg)) { + return true; + } + } + + return false; + } + } + private partial bool TryHandleIntrinsic ( MethodProxy calledMethod, MultiValue instanceValue, @@ -130,6 +169,17 @@ private partial bool TryHandleIntrinsic ( break; } + case IntrinsicId.TypeMapping_GetOrCreateProxyTypeMapping: + case IntrinsicId.TypeMapping_GetOrCreateExternalTypeMapping: { + ITypeSymbol typeMapGroup = calledMethod.Method.TypeArguments[0]; + if (HasGenericTypeSymbolsVisitor.Instance.Visit(typeMapGroup)) { + // We only support GetOrCreateExternalTypeMapping or GetOrCreateProxyTypeMapping for a fully specified type. + _diagnosticContext.AddDiagnostic (DiagnosticId.TypeMapGroupTypeCannotBeStaticallyDetermined, + typeMapGroup.GetDisplayName ()); + } + break; + } + // Some intrinsics are unimplemented by the analyzer. // These will fall back to the usual return-value handling. case IntrinsicId.Array_CreateInstance: @@ -148,6 +198,7 @@ private partial bool TryHandleIntrinsic ( case IntrinsicId.RuntimeReflectionExtensions_GetMethodInfo: break; + default: return false; } diff --git a/src/tools/illink/src/ILLink.Shared/DiagnosticId.cs b/src/tools/illink/src/ILLink.Shared/DiagnosticId.cs index 9e65a71e1a33c7..54cdf18670ac3c 100644 --- a/src/tools/illink/src/ILLink.Shared/DiagnosticId.cs +++ b/src/tools/illink/src/ILLink.Shared/DiagnosticId.cs @@ -188,6 +188,7 @@ public enum DiagnosticId RedundantSuppression = 2121, TypeNameIsNotAssemblyQualified = 2122, RequiresUnreferencedCodeOnEntryPoint = 2123, + TypeMapGroupTypeCannotBeStaticallyDetermined = 2124, _EndTrimAnalysisWarningsSentinel, // Single-file diagnostic ids. diff --git a/src/tools/illink/src/ILLink.Shared/SharedStrings.resx b/src/tools/illink/src/ILLink.Shared/SharedStrings.resx index 920b71e7126080..40c921ca706cde 100644 --- a/src/tools/illink/src/ILLink.Shared/SharedStrings.resx +++ b/src/tools/illink/src/ILLink.Shared/SharedStrings.resx @@ -1089,6 +1089,12 @@ 'DynamicallyAccessedMemberAttribute' on '{0}' or one of its base types references compiler-generated member '{1}'. + + The provided generic argument cannot be statically determined. + + + Type '{0}' must not contain signature variables to be used as a type map group. + Avoid accessing Assembly file path when publishing as a single file diff --git a/src/tools/illink/src/ILLink.Shared/TrimAnalysis/IntrinsicId.cs b/src/tools/illink/src/ILLink.Shared/TrimAnalysis/IntrinsicId.cs index 8056970932704e..2296bc312f56fd 100644 --- a/src/tools/illink/src/ILLink.Shared/TrimAnalysis/IntrinsicId.cs +++ b/src/tools/illink/src/ILLink.Shared/TrimAnalysis/IntrinsicId.cs @@ -352,5 +352,13 @@ internal enum IntrinsicId /// /// Delegate_get_Method, + /// + /// . + /// + TypeMapping_GetOrCreateExternalTypeMapping, + /// + /// . + /// + TypeMapping_GetOrCreateProxyTypeMapping, } } diff --git a/src/tools/illink/src/ILLink.Shared/TrimAnalysis/Intrinsics.cs b/src/tools/illink/src/ILLink.Shared/TrimAnalysis/Intrinsics.cs index 2d672707ac64fc..74fe628b668c09 100644 --- a/src/tools/illink/src/ILLink.Shared/TrimAnalysis/Intrinsics.cs +++ b/src/tools/illink/src/ILLink.Shared/TrimAnalysis/Intrinsics.cs @@ -418,6 +418,18 @@ public static IntrinsicId GetIntrinsicIdForMethod (MethodProxy calledMethod) && calledMethod.HasMetadataParametersCount (0) => IntrinsicId.Delegate_get_Method, + // static System.Runtime.InteropServices.TypeMapping.GetOrCreateExternalTypeMapping () + "GetOrCreateExternalTypeMapping" when calledMethod.IsDeclaredOnType ("System.Runtime.InteropServices.TypeMapping") + && calledMethod.IsStatic () + && calledMethod.HasGenericParametersCount (1) + => IntrinsicId.TypeMapping_GetOrCreateExternalTypeMapping, + + // static System.Runtime.InteropServices.TypeMapping.GetOrCreateProxyTypeMapping () + "GetOrCreateProxyTypeMapping" when calledMethod.IsDeclaredOnType ("System.Runtime.InteropServices.TypeMapping") + && calledMethod.IsStatic () + && calledMethod.HasGenericParametersCount (1) + => IntrinsicId.TypeMapping_GetOrCreateProxyTypeMapping, + _ => IntrinsicId.None, }; } diff --git a/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/TestChecker.cs b/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/TestChecker.cs index c50d24f136051d..28d07e71406a08 100644 --- a/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/TestChecker.cs +++ b/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/TestChecker.cs @@ -168,9 +168,14 @@ private void ValidateDiagnostics (CSharpSyntaxNode memberSyntax, SyntaxList ("TrimTargetIsTarget", typeof (TargetAndTrimTarget), typeof (TargetAndTrimTarget))] +[assembly: TypeMap ("TrimTargetIsUnrelated", typeof (TargetType), typeof (TrimTarget))] +[assembly: TypeMap ("TrimTargetIsAllocatedNoTypeCheckClass", typeof (TargetType2), typeof (AllocatedNoTypeCheckClass))] +[assembly: TypeMap ("TrimTargetIsAllocatedNoTypeCheckStruct", typeof (TargetType3), typeof (AllocatedNoTypeCheckStruct))] +[assembly: TypeMap ("TrimTargetIsUnreferenced", typeof (UnreferencedTargetType), typeof (UnreferencedTrimTarget))] +[assembly: TypeMapAssociation (typeof (SourceClass), typeof (ProxyType))] +[assembly: TypeMapAssociation (typeof (TypeCheckOnlyClass), typeof (ProxyType2))] +[assembly: TypeMapAssociation (typeof (AllocatedNoBoxStructType), typeof (ProxyType3))] +[assembly: TypeMapAssociation (typeof (I), typeof (IImpl))] +[assembly: TypeMapAssociation (typeof (IInterfaceWithDynamicImpl), typeof (IDynamicImpl))] + +[assembly: TypeMap ("UnusedName", typeof (UnusedTargetType), typeof (TrimTarget))] +[assembly: TypeMapAssociation (typeof (UnusedSourceClass), typeof (UnusedProxyType))] + +namespace Mono.Linker.Tests.Cases.Reflection +{ + [Kept] + [IgnoreTestCase ("Trimmer support is currently not implemented", IgnoredBy = Tool.Trimmer)] + class TypeMap + { + [Kept] + [ExpectedWarning ("IL2057", "Unrecognized value passed to the parameter 'typeName' of method 'System.Type.GetType(String)'")] + public static void Main (string[] args) + { + object t = Activator.CreateInstance (Type.GetType (args[1])); + if (t is TargetAndTrimTarget) { + Console.WriteLine ("Type deriving from TargetAndTrimTarget instantiated."); + } else if (t is TrimTarget) { + Console.WriteLine ("Type deriving from TrimTarget instantiated."); + } else if (t is IInterfaceWithDynamicImpl d) { + d.Method (); + } else if (t is TypeCheckOnlyClass typeCheckOnlyClass) { + Console.WriteLine ("Type deriving from TypeCheckOnlyClass instantiated."); + } + + Console.WriteLine ("Hash code of SourceClass instance: " + new SourceClass ().GetHashCode ()); + Console.WriteLine ("Hash code of UsedClass instance: " + new UsedClass ().GetHashCode ()); + Console.WriteLine ("Hash code of AllocatedNoTypeCheckClass instance: " + new AllocatedNoTypeCheckClass ().GetHashCode ()); + Console.WriteLine ("Hash code of AllocatedNoTypeCheckStruct instance: " + new AllocatedNoTypeCheckStruct ().GetHashCode ()); + + Console.WriteLine (TypeMapping.GetOrCreateExternalTypeMapping ()); + Console.WriteLine (GetExternalTypeMap ()); + + var proxyMap = TypeMapping.GetOrCreateProxyTypeMapping (); + + AllocatedNoBoxStructType allocatedNoBoxStructType = new AllocatedNoBoxStructType (Random.Shared.Next ()); + Console.WriteLine ("AllocatedNoBoxStructType value: " + allocatedNoBoxStructType.Value); + Console.WriteLine (proxyMap[typeof (AllocatedNoBoxStructType)]); + } + + [Kept] + [ExpectedWarning ("IL2124", "Type 'T' must not contain signature variables to be used as a type map group.")] + private static IReadOnlyDictionary GetExternalTypeMap () + { + return TypeMapping.GetOrCreateExternalTypeMapping (); + } + } + + [Kept (By = Tool.Trimmer)] + class UsedTypeMap; + + [Kept] + class TargetAndTrimTarget; + + [Kept] + class TargetType; + + [Kept] // This is kept by NativeAot by the scanner. It is not kept during codegen. + class TrimTarget; + + class UnreferencedTargetType; + + class UnreferencedTrimTarget; + + [Kept] + [KeptMember (".ctor()")] + class SourceClass; + + [Kept] + class ProxyType; + + class UnusedTypeMap; + class UnusedTargetType; + class UnusedSourceClass; + class UnusedProxyType; + + interface I; + + interface IImpl : I; + + [Kept] + [KeptMember (".ctor()")] + class UsedClass : IImpl; + + [Kept] + interface IInterfaceWithDynamicImpl + { + [Kept (By = Tool.Trimmer)] + void Method (); + } + + [Kept] + [KeptInterface (typeof (IInterfaceWithDynamicImpl))] + [KeptAttributeAttribute (typeof (DynamicInterfaceCastableImplementationAttribute), By = Tool.Trimmer)] + [DynamicInterfaceCastableImplementation] + interface IDynamicImpl : IInterfaceWithDynamicImpl + { + [Kept] + void IInterfaceWithDynamicImpl.Method () + { + } + } + + [Kept] + [KeptMember(".ctor()")] + class AllocatedNoTypeCheckClass; + + [Kept] + struct AllocatedNoTypeCheckStruct; + + [Kept] + class TargetType2; + + [Kept] + class TargetType3; + + [Kept] + class TypeCheckOnlyClass; + + class ProxyType2; + + [Kept] + struct AllocatedNoBoxStructType + { + [Kept] + public AllocatedNoBoxStructType (int value) + { + Value = value; + } + + [Kept] + public int Value { [Kept] get; } + } + + [Kept] + class ProxyType3; +} + +// Polyfill for the type map types until we use an LKG runtime that has it. +namespace System.Runtime.InteropServices +{ + [Kept (By = Tool.Trimmer)] + [KeptBaseType (typeof (Attribute), By = Tool.Trimmer)] + [KeptAttributeAttribute (typeof (AttributeUsageAttribute), By = Tool.Trimmer)] + [AttributeUsage (AttributeTargets.Assembly, AllowMultiple = true)] + public sealed class TypeMapAttribute : Attribute + { + [Kept (By = Tool.Trimmer)] + public TypeMapAttribute (string value, Type target) { } + + [Kept (By = Tool.Trimmer)] + [KeptAttributeAttribute (typeof (RequiresUnreferencedCodeAttribute), By = Tool.Trimmer)] + [RequiresUnreferencedCode ("Interop types may be removed by trimming")] + public TypeMapAttribute (string value, Type target, Type trimTarget) { } + } + + [Kept (By = Tool.Trimmer)] + [KeptBaseType (typeof (Attribute), By = Tool.Trimmer)] + [KeptAttributeAttribute (typeof (AttributeUsageAttribute), By = Tool.Trimmer)] + [AttributeUsage (AttributeTargets.Assembly, AllowMultiple = true)] + public sealed class TypeMapAssociationAttribute : Attribute + { + [Kept (By = Tool.Trimmer)] + public TypeMapAssociationAttribute (Type source, Type proxy) { } + } + + [Kept(By = Tool.Trimmer)] + public static class TypeMapping + { + [Kept(By = Tool.Trimmer)] + [KeptAttributeAttribute (typeof (RequiresUnreferencedCodeAttribute), By = Tool.Trimmer)] + [RequiresUnreferencedCode ("Interop types may be removed by trimming")] + public static IReadOnlyDictionary GetOrCreateExternalTypeMapping () + { + throw new NotImplementedException (); + } + + [Kept(By = Tool.Trimmer)] + [KeptAttributeAttribute (typeof (RequiresUnreferencedCodeAttribute), By = Tool.Trimmer)] + [RequiresUnreferencedCode ("Interop types may be removed by trimming")] + public static IReadOnlyDictionary GetOrCreateProxyTypeMapping () + { + throw new NotImplementedException (); + } + } +} +