File: Compiler\DependencyAnalysis\ExternalTypeMapNode.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 System.Collections.Immutable;
using System.Diagnostics;
using ILCompiler.DependencyAnalysisFramework;
using Internal.NativeFormat;
using Internal.TypeSystem;

namespace ILCompiler.DependencyAnalysis
{
    internal sealed class ExternalTypeMapNode : SortableDependencyNode, IExternalTypeMapNode
    {
        private readonly IEnumerable<KeyValuePair<string, (TypeDesc targetType, TypeDesc trimmingTargetType)>> _mapEntries;

        public ExternalTypeMapNode(TypeDesc typeMapGroup, IEnumerable<KeyValuePair<string, (TypeDesc targetType, TypeDesc trimmingTargetType)>> 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<CombinedDependencyListEntry> GetConditionalStaticDependencies(NodeFactory context)
        {
            foreach (var entry in _mapEntries)
            {
                var (targetType, trimmingTargetType) = entry.Value;
                if (trimmingTargetType is not null)
                {
                    TypeDesc effectiveTrimTargetType = GetEffectiveTrimTargetType(trimmingTargetType);

                    yield return new CombinedDependencyListEntry(
                        context.MetadataTypeSymbol(targetType),
                        context.NecessaryTypeSymbol(effectiveTrimTargetType),
                        "Type in external type map is cast target");

                    // If the trimming target type has a canonical form, it could be created at runtime by the type loader.
                    // If there is a type loader template for it, create the generic type instantiation eagerly.
                    TypeDesc canonTrimmingType = effectiveTrimTargetType.ConvertToCanonForm(CanonicalFormKind.Specific);
                    if (canonTrimmingType != effectiveTrimTargetType && GenericTypesTemplateMap.IsEligibleToHaveATemplate(canonTrimmingType))
                    {
                        yield return new CombinedDependencyListEntry(
                            context.NecessaryTypeSymbol(effectiveTrimTargetType),
                            context.NativeLayout.TemplateTypeLayout(canonTrimmingType),
                            "External type map trim target that could be loaded at runtime");
                    }
                    else if (effectiveTrimTargetType is ArrayType arrayType)
                    {
                        // Some arrays don't have array templates (e.g. multidimensional arrays, arrays of pointers).
                        // If the element type is template-loadable, the runtime can still construct the array type.
                        TypeDesc effectiveElementType = GetEffectiveTrimTargetType(arrayType.ElementType);
                        TypeDesc canonElementType = effectiveElementType.ConvertToCanonForm(CanonicalFormKind.Specific);
                        if (canonElementType != effectiveElementType && GenericTypesTemplateMap.IsEligibleToHaveATemplate(canonElementType))
                        {
                            yield return new CombinedDependencyListEntry(
                                context.NecessaryTypeSymbol(effectiveTrimTargetType),
                                context.NativeLayout.TemplateTypeLayout(canonElementType),
                                "External type map array trim target with template-loadable element type");
                        }

                        // Array types that aren't eligible for templates (MdArrays, pointer/fnptr-element SzArrays)
                        // can be constructed at runtime from just the element type's MethodTable using hardcoded
                        // templates (typeof(object[,]) for MdArrays, typeof(char*[]) for pointer arrays).
                        // If the element type is reachable, consider the array type reachable as well.
                        if (!GenericTypesTemplateMap.IsArrayTypeEligibleForTemplate(arrayType))
                        {
                            yield return new CombinedDependencyListEntry(
                                context.NecessaryTypeSymbol(effectiveTrimTargetType),
                                context.NecessaryTypeSymbol(effectiveElementType),
                                "Array without template can be constructed at runtime from element type");
                        }
                    }
                }
            }
        }

        public override IEnumerable<DependencyListEntry> GetStaticDependencies(NodeFactory context)
        {
            foreach (var entry in _mapEntries)
            {
                var (targetType, trimmingTargetType) = entry.Value;
                if (trimmingTargetType is null)
                {
                    yield return new DependencyListEntry(
                        context.MetadataTypeSymbol(targetType),
                        "External type map entry target type");
                }
            }
        }

        public override IEnumerable<CombinedDependencyListEntry> SearchDynamicDependencies(List<DependencyNodeCore<NodeFactory>> markedNodes, int firstNode, NodeFactory context) => Array.Empty<CombinedDependencyListEntry>();
        protected override string GetName(NodeFactory context) => $"External type map: {TypeMapGroup}";

        public override int ClassCode => -785190502;

        public override 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 (trimmingTargetType is null
                    || factory.NecessaryTypeSymbol(GetEffectiveTrimTargetType(trimmingTargetType)).Marked)
                {
                    IEETypeNode targetNode = factory.MetadataTypeSymbol(targetType);
                    Debug.Assert(targetNode.Marked);
                    yield return (entry.Key, targetNode);
                }
            }
        }

        // Strip non-array parameterized wrappers (pointers, byrefs) to get the effective
        // trimming target type. Arrays are preserved so trim dependencies can be conditioned
        // on array existence rather than just element type reachability.
        private static TypeDesc GetEffectiveTrimTargetType(TypeDesc trimmingTargetType)
        {
            while (trimmingTargetType is ParameterizedType parameterized && !trimmingTargetType.IsArray)
                trimmingTargetType = parameterized.ParameterType;
            return trimmingTargetType;
        }

        public Vertex CreateTypeMap(NodeFactory factory, NativeWriter writer, Section section, INativeFormatTypeReferenceProvider externalReferences)
        {
            VertexHashtable typeMapHashTable = new();

            foreach ((string key, IEETypeNode valueNode) in GetMarkedEntries(factory))
            {
                Vertex keyVertex = writer.GetStringConstant(key);
                Vertex valueVertex = externalReferences.EncodeReferenceToType(writer, valueNode.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 = externalReferences.EncodeReferenceToType(writer, 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));
    }
}