File: Compiler\AnalysisBasedMetadataManager.cs
Web Access
Project: src\src\runtime\src\coreclr\tools\aot\ILCompiler.Compiler\ILCompiler.Compiler.csproj (ILCompiler.Compiler)
// 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 Internal.TypeSystem;

using ILCompiler.Metadata;
using ILCompiler.DependencyAnalysis;

using Debug = System.Diagnostics.Debug;
using EcmaModule = Internal.TypeSystem.Ecma.EcmaModule;
using CustomAttributeHandle = System.Reflection.Metadata.CustomAttributeHandle;
using ExportedTypeHandle = System.Reflection.Metadata.ExportedTypeHandle;
using FlowAnnotations = ILLink.Shared.TrimAnalysis.FlowAnnotations;
using ParameterHandle = System.Reflection.Metadata.ParameterHandle;

namespace ILCompiler
{
    /// <summary>
    /// A metadata manager that knows the full set of metadata ahead of time.
    /// </summary>
    public sealed class AnalysisBasedMetadataManager : MetadataManager, ICompilationRootProvider
    {
        private readonly List<ModuleDesc> _modulesWithMetadata;
        private readonly List<TypeDesc> _forcedTypes;

        private readonly Dictionary<TypeDesc, MetadataCategory> _reflectableTypes = new Dictionary<TypeDesc, MetadataCategory>();
        private readonly Dictionary<MethodDesc, MetadataCategory> _reflectableMethods = new Dictionary<MethodDesc, MetadataCategory>();
        private readonly Dictionary<FieldDesc, MetadataCategory> _reflectableFields = new Dictionary<FieldDesc, MetadataCategory>();
        private readonly HashSet<ReflectableCustomAttribute> _reflectableAttributes = new HashSet<ReflectableCustomAttribute>();
        private readonly HashSet<ReflectableParameter> _reflectableParameters = new HashSet<ReflectableParameter>();

        public AnalysisBasedMetadataManager(CompilerTypeSystemContext typeSystemContext)
            : this(typeSystemContext, new FullyBlockedMetadataBlockingPolicy(),
                new FullyBlockedManifestResourceBlockingPolicy(), null, new NoStackTraceEmissionPolicy(),
                new NoDynamicInvokeThunkGenerationPolicy(), null, Array.Empty<ModuleDesc>(), Array.Empty<TypeDesc>(),
                Array.Empty<ReflectableEntity<TypeDesc>>(), Array.Empty<ReflectableEntity<MethodDesc>>(),
                Array.Empty<ReflectableEntity<FieldDesc>>(), Array.Empty<ReflectableCustomAttribute>(),
                Array.Empty<ReflectableParameter>(),
                default)
        {
        }

        public AnalysisBasedMetadataManager(
            CompilerTypeSystemContext typeSystemContext,
            MetadataBlockingPolicy blockingPolicy,
            ManifestResourceBlockingPolicy resourceBlockingPolicy,
            string logFile,
            StackTraceEmissionPolicy stackTracePolicy,
            DynamicInvokeThunkGenerationPolicy invokeThunkGenerationPolicy,
            FlowAnnotations flowAnnotations,
            IEnumerable<ModuleDesc> modulesWithMetadata,
            IEnumerable<TypeDesc> forcedTypes,
            IEnumerable<ReflectableEntity<TypeDesc>> reflectableTypes,
            IEnumerable<ReflectableEntity<MethodDesc>> reflectableMethods,
            IEnumerable<ReflectableEntity<FieldDesc>> reflectableFields,
            IEnumerable<ReflectableCustomAttribute> reflectableAttributes,
            IEnumerable<ReflectableParameter> reflectableParameters,
            MetadataManagerOptions options)
            : base(typeSystemContext, blockingPolicy, resourceBlockingPolicy, logFile, stackTracePolicy, invokeThunkGenerationPolicy, options, flowAnnotations)
        {
            _modulesWithMetadata = new List<ModuleDesc>(modulesWithMetadata);
            _forcedTypes = new List<TypeDesc>(forcedTypes);

            foreach (var refType in reflectableTypes)
            {
                _reflectableTypes.Add(refType.Entity, refType.Category);
            }

            foreach (var refMethod in reflectableMethods)
            {
                // Asking for description or runtime mapping for a member without asking
                // for the owning type would mean we can't actually satisfy the request.
                Debug.Assert((refMethod.Category & MetadataCategory.Description) == 0
                    || (_reflectableTypes[refMethod.Entity.OwningType] & MetadataCategory.Description) != 0);
                Debug.Assert((refMethod.Category & MetadataCategory.RuntimeMapping) == 0
                    || (_reflectableTypes[refMethod.Entity.OwningType] & MetadataCategory.RuntimeMapping) != 0);
                _reflectableMethods.Add(refMethod.Entity, refMethod.Category);
            }

            foreach (var refField in reflectableFields)
            {
                // Asking for description or runtime mapping for a member without asking
                // for the owning type would mean we can't actually satisfy the request.
                Debug.Assert((refField.Category & MetadataCategory.Description) == 0
                    || (_reflectableTypes[refField.Entity.OwningType] & MetadataCategory.Description) != 0);
                Debug.Assert((refField.Category & MetadataCategory.RuntimeMapping) == 0
                    || (_reflectableTypes[refField.Entity.OwningType] & MetadataCategory.RuntimeMapping) != 0);
                _reflectableFields.Add(refField.Entity, refField.Category);
            }

            foreach (var refAttribute in reflectableAttributes)
            {
                _reflectableAttributes.Add(refAttribute);
            }

            foreach (var refParameter in reflectableParameters)
            {
                _reflectableParameters.Add(refParameter);
            }

#if DEBUG
            HashSet<ModuleDesc> moduleHash = new HashSet<ModuleDesc>(_modulesWithMetadata);
            foreach (var refType in reflectableTypes)
            {
                // The instantiated types need to agree on the Description bit with the definition.
                // GetMetadataCategory relies on that.
                Debug.Assert((GetMetadataCategory(refType.Entity.GetTypeDefinition()) & MetadataCategory.Description)
                    == (GetMetadataCategory(refType.Entity) & MetadataCategory.Description));

                Debug.Assert((refType.Category & MetadataCategory.Description) == 0 ||
                    !(refType.Entity is MetadataType) || moduleHash.Contains(((MetadataType)refType.Entity).Module));
            }

            foreach (var refMethod in reflectableMethods)
            {
                // The instantiated methods need to agree on the Description bit with the definition.
                // GetMetadataCategory relies on that.
                Debug.Assert((GetMetadataCategory(refMethod.Entity.GetTypicalMethodDefinition()) & MetadataCategory.Description)
                    == (GetMetadataCategory(refMethod.Entity) & MetadataCategory.Description));

                // Canonical form of the method needs to agree with the logical form
                Debug.Assert(GetMetadataCategory(refMethod.Entity) == GetMetadataCategory(refMethod.Entity.GetCanonMethodTarget(CanonicalFormKind.Specific)));
            }

            foreach (var refField in reflectableFields)
            {
                // The instantiated fields need to agree on the Description bit with the definition.
                // GetMetadataCategory relies on that.
                Debug.Assert((GetMetadataCategory(refField.Entity.GetTypicalFieldDefinition()) & MetadataCategory.Description)
                    == (GetMetadataCategory(refField.Entity) & MetadataCategory.Description));
            }
#endif
        }

        public override IEnumerable<ModuleDesc> GetCompilationModulesWithMetadata()
        {
            return _modulesWithMetadata;
        }

        protected override void ComputeMetadata(NodeFactory factory,
            out byte[] metadataBlob,
            out List<MetadataMapping<MetadataType>> typeMappings,
            out List<MetadataMapping<MethodDesc>> methodMappings,
            out Dictionary<MethodDesc, int> methodMetadataMappings,
            out List<MetadataMapping<FieldDesc>> fieldMappings,
            out Dictionary<FieldDesc, int> fieldMetadataMappings,
            out List<StackTraceMapping> stackTraceMapping,
            out List<ReflectionStackTraceMapping> reflectionStackTraceMapping)
        {
            ComputeMetadata(new Policy(_blockingPolicy, this), factory,
                out metadataBlob,
                out typeMappings,
                out methodMappings,
                out methodMetadataMappings,
                out fieldMappings,
                out fieldMetadataMappings,
                out stackTraceMapping,
                out reflectionStackTraceMapping);
        }

        protected sealed override MetadataCategory GetMetadataCategory(MethodDesc method)
        {
            if (_reflectableMethods.TryGetValue(method, out MetadataCategory value))
                return value;
            return 0;
        }

        protected sealed override MetadataCategory GetMetadataCategory(TypeDesc type)
        {
            if (_reflectableTypes.TryGetValue(type, out MetadataCategory value))
                return value;
            return 0;
        }

        protected sealed override MetadataCategory GetMetadataCategory(FieldDesc field)
        {
            if (_reflectableFields.TryGetValue(field, out MetadataCategory value))
                return value;
            return 0;
        }

        protected override IEnumerable<FieldDesc> GetFieldsWithRuntimeMapping()
        {
            foreach (var pair in _reflectableFields)
            {
                if ((pair.Value & MetadataCategory.RuntimeMapping) != 0)
                    yield return pair.Key;
            }
        }

        void ICompilationRootProvider.AddCompilationRoots(IRootingServiceProvider rootProvider)
        {
            // We go over all the types and members that need a runtime artifact present in the
            // compiled executable and root it.

            const string reason = "Reflection";

            foreach (var type in _forcedTypes)
            {
                rootProvider.AddReflectionRoot(type, reason);
            }

            foreach (var pair in _reflectableMethods)
            {
                if ((pair.Value & MetadataCategory.RuntimeMapping) != 0)
                {
                    MethodDesc method = pair.Key;
                    rootProvider.AddReflectionRoot(method, reason);
                }
            }

            foreach (var pair in _reflectableFields)
            {
                if ((pair.Value & MetadataCategory.RuntimeMapping) != 0)
                {
                    FieldDesc field = pair.Key;
                    rootProvider.AddReflectionRoot(field, reason);
                }
            }
        }

        private struct Policy : IMetadataPolicy
        {
            private readonly MetadataBlockingPolicy _blockingPolicy;
            private readonly AnalysisBasedMetadataManager _parent;

            public Policy(MetadataBlockingPolicy blockingPolicy,
                AnalysisBasedMetadataManager parent)
            {
                _blockingPolicy = blockingPolicy;
                _parent = parent;
            }

            public bool GeneratesMetadata(FieldDesc fieldDef)
            {
                return (_parent.GetMetadataCategory(fieldDef) & MetadataCategory.Description) != 0;
            }

            public bool GeneratesMetadata(MethodDesc methodDef)
            {
                return (_parent.GetMetadataCategory(methodDef) & MetadataCategory.Description) != 0;
            }

            public bool GeneratesMetadata(MetadataType typeDef)
            {
                return (_parent.GetMetadataCategory(typeDef) & MetadataCategory.Description) != 0;
            }

            public bool GeneratesMetadata(EcmaModule module, CustomAttributeHandle caHandle)
            {
                return _parent._reflectableAttributes.Contains(new ReflectableCustomAttribute(module, caHandle));
            }

            public bool GeneratesMetadata(EcmaModule module, ParameterHandle paramHandle)
            {
                return _parent._reflectableParameters.Contains(new ReflectableParameter(module, paramHandle));
            }

            public bool GeneratesMetadata(EcmaModule module, ExportedTypeHandle exportedTypeHandle)
            {
                // We'll possibly need to do something else here if we ever use this MetadataManager
                // with compilation modes that generate multiple metadata blobs.
                // (Multi-module or .NET Native style shared library.)
                // We are currently missing type forwarders pointing to the other blobs.
                var targetType = (MetadataType)module.GetObject(exportedTypeHandle, NotFoundBehavior.ReturnNull);
                if (targetType == null)
                {
                    // No harm in generating a forwarder that didn't resolve.
                    // We'll get matching behavior at runtime.
                    return true;
                }

                return GeneratesMetadata(targetType);
            }

            public bool GeneratesInterfaceImpl(MetadataType typeDef, MetadataType interfaceImpl)
            {
                return _parent.IsInterfaceUsed(interfaceImpl.GetTypeDefinition());
            }

            public bool IsBlocked(MetadataType typeDef)
            {
                return _blockingPolicy.IsBlocked(typeDef);
            }

            public bool IsBlocked(MethodDesc methodDef)
            {
                return _blockingPolicy.IsBlocked(methodDef);
            }
        }
    }

    public struct ReflectableEntity<TEntity>
    {
        public readonly TEntity Entity;
        public readonly MetadataCategory Category;

        public ReflectableEntity(TEntity entity, MetadataCategory category)
        {
            Entity = entity;
            Category = category;
        }
    }

    public struct ReflectableCustomAttribute : IEquatable<ReflectableCustomAttribute>
    {
        public readonly EcmaModule Module;
        public readonly CustomAttributeHandle CustomAttributeHandle;

        public ReflectableCustomAttribute(EcmaModule module, CustomAttributeHandle caHandle)
            => (Module, CustomAttributeHandle) = (module, caHandle);

        public bool Equals(ReflectableCustomAttribute other)
            => other.Module == Module && other.CustomAttributeHandle == CustomAttributeHandle;
        public override bool Equals(object obj)
            => obj is ReflectableCustomAttribute other && Equals(other);
        public override int GetHashCode() => Module.GetHashCode() ^ CustomAttributeHandle.GetHashCode();
    }

    public struct ReflectableParameter : IEquatable<ReflectableParameter>
    {
        public readonly EcmaModule Module;
        public readonly ParameterHandle ParameterHandle;

        public ReflectableParameter(EcmaModule module, ParameterHandle paramHandle)
            => (Module, ParameterHandle) = (module, paramHandle);

        public bool Equals(ReflectableParameter other)
            => other.Module == Module && other.ParameterHandle == ParameterHandle;
        public override bool Equals(object obj)
            => obj is ReflectableParameter other && Equals(other);
        public override int GetHashCode() => Module.GetHashCode() ^ ParameterHandle.GetHashCode();
    }
}