File: Compiler\DependencyAnalysis\ReadyToRun\InliningInfoNode.cs
Web Access
Project: src\src\runtime\src\coreclr\tools\aot\ILCompiler.ReadyToRun\ILCompiler.ReadyToRun.csproj (ILCompiler.ReadyToRun)
// 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.IO;
using System.Reflection.Metadata.Ecma335;
using ILCompiler.ReadyToRun.TypeSystem;
using Internal;
using Internal.NativeFormat;
using Internal.ReadyToRunConstants;
using Internal.Text;
using Internal.TypeSystem;
using Internal.TypeSystem.Ecma;

namespace ILCompiler.DependencyAnalysis.ReadyToRun
{
    /// <summary>
    /// Stores information about what methods got inlined into other methods.
    /// </summary>
    public class InliningInfoNode : ModuleSpecificHeaderTableNode
    {
        public enum InfoType
        {
            InliningInfo2,
            CrossModuleInliningForCrossModuleDataOnly,
            CrossModuleAllMethods
        }

        private readonly InfoType _inlineInfoType;
        private ReadyToRunSymbolNodeFactory _symbolNodeFactory;

        public InliningInfoNode(EcmaModule module, InfoType inlineInfoType) : base(module)
        {
            _inlineInfoType = inlineInfoType;
            if (AllowCrossModuleInlines)
            {
                Debug.Assert(module == null); // Cross module inlining always covers all modules
            }
            else
            {
                Debug.Assert(module != null); // InliningInfo2 is restricted to a single module at a time
            }
        }

        public void Initialize(ReadyToRunSymbolNodeFactory symbolNodeFactory)
        {
            _symbolNodeFactory = symbolNodeFactory;
        }

        protected override string ModuleSpecificName => "__ReadyToRunInliningInfoTable__";

        private bool AllowCrossModuleInlines => _inlineInfoType == InfoType.CrossModuleAllMethods || _inlineInfoType == InfoType.CrossModuleInliningForCrossModuleDataOnly;
        private bool ReportAllInlinesInSearch => _inlineInfoType == InfoType.CrossModuleAllMethods;

        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<byte>(), Array.Empty<Relocation>(), 1, new ISymbolDefinitionNode[] { this });

            Dictionary<MethodDesc, HashSet<MethodDesc>> inlineeToInliners = new Dictionary<MethodDesc, HashSet<MethodDesc>>();

            // Build a map from inlinee to the list of inliners
            // We are only interested in the generic definitions of these.
            foreach (MethodWithGCInfo methodNode in factory.EnumerateCompiledMethods(_module, CompiledMethodCategory.All))
            {
                MethodDesc[] inlinees = methodNode.InlinedMethods;
                if (inlinees.Length == 0)
                {
                    continue;
                }
                MethodDesc inliner = methodNode.Method;
                EcmaMethod inlinerDefinition = (EcmaMethod)inliner.GetPrimaryMethodDesc().GetTypicalMethodDefinition();

                if (inlinerDefinition.IsNonVersionable())
                {
                    // Non-versionable methods don't need to be reported
                    continue;
                }

                // Only encode inlining info for inliners within the active module, or if cross module inline format is in use
                Debug.Assert(AllowCrossModuleInlines || (inlinerDefinition.Module == _module));

                bool inlinerReportAllVersionsWithInlinee = !AllowCrossModuleInlines || factory.CompilationModuleGroup.CrossModuleCompileable(inlinerDefinition);

                foreach (MethodDesc inlinee in inlinees)
                {
                    MethodDesc inlineeDefinition = inlinee.GetTypicalMethodDefinition();
                    if (!(inlineeDefinition is EcmaMethod or AsyncMethodVariant))
                    {
                        // We don't record non-ECMA methods because they don't have tokens that
                        // diagnostic tools could reason about anyway.
                        continue;
                    }

                    if (inlinee.IsNonVersionable())
                    {
                        // Non-versionable methods don't need to be reported
                        continue;
                    }

                    if (ReportAllInlinesInSearch)
                    {
                        // We'll definitely track this inline
                    }
                    else if (factory.CompilationModuleGroup.VersionsWithMethodBody(inlineeDefinition))
                    {
                        if (!inlinerReportAllVersionsWithInlinee)
                        {
                            // We won't report this method
                            continue;
                        }
                    }
                    else
                    {
                        Debug.Assert(factory.CompilationModuleGroup.CrossModuleInlineable(inlineeDefinition));
                        if (_inlineInfoType != InfoType.CrossModuleInliningForCrossModuleDataOnly)
                        {
                            // We won't report this method
                            continue;
                        }
                    }

                    if (!inlineeToInliners.TryGetValue(inlineeDefinition, out HashSet<MethodDesc> inliners))
                    {
                        inliners = new HashSet<MethodDesc>();
                        inlineeToInliners.Add(inlineeDefinition, inliners);
                    }
                    inliners.Add(inlinerDefinition);
                }
            }

            // Serialize the map as a hash table
            NativeWriter writer = new NativeWriter();
            Section section = writer.NewSection();

            VertexHashtable hashtable = new VertexHashtable();
            section.Place(hashtable);

            foreach (var inlineeWithInliners in inlineeToInliners)
            {
                MethodDesc inlinee = inlineeWithInliners.Key;
                EcmaMethod ecmaInlinee = (EcmaMethod)inlinee.GetPrimaryMethodDesc();
                int inlineeRid = MetadataTokens.GetRowNumber(ecmaInlinee.Handle);
                int hashCode;

                if (AllowCrossModuleInlines)
                {
                    // CrossModuleInlineInfo format
                    hashCode = inlinee.GetHashCode();
                }
                else
                {
                    // InliningInfo2 format
                    hashCode = VersionResilientHashCode.ModuleNameHashCode(ecmaInlinee.Module);
                    hashCode ^= inlineeRid;
                }

                var sig = new VertexSequence();

                if (!AllowCrossModuleInlines)
                {
                    // Format of the sequence:
                    // FOR InliningInfo2 table format
                    //    Inlinee RID with flag in the lowest bit
                    //    - if flag is set, followed by module ID
                    //    Followed by inliner RIDs deltas with flag in the lowest bit
                    //    - if flag is set, followed by module ID
                    Debug.Assert(_module != null);
                    bool isForeignInlinee = ecmaInlinee.Module != _module;
                    sig.Append(new UnsignedConstant((uint)(inlineeRid << 1 | (isForeignInlinee ? 1 : 0))));
                    if (isForeignInlinee)
                    {
                        sig.Append(new UnsignedConstant((uint)factory.ManifestMetadataTable.ModuleToIndex(ecmaInlinee.Module)));
                    }

                    // We're only concerned with metadata here, so we can convert all to EcmaMethod and lose info about AsyncVariant vs Task-Returning
                    List<EcmaMethod> sortedInliners = new List<EcmaMethod>(inlineeWithInliners.Value.Count);
                    foreach (var inliner in inlineeWithInliners.Value)
                    {
                        sortedInliners.Add((EcmaMethod)inliner.GetPrimaryMethodDesc());
                    }
                    sortedInliners.MergeSort((a, b) =>
                    {
                        if (a == b)
                            return 0;

                        int aRid = MetadataTokens.GetRowNumber(a.Handle);
                        int bRid = MetadataTokens.GetRowNumber(b.Handle);
                        if (aRid < bRid)
                            return -1;
                        else if (aRid > bRid)
                            return 1;

                        int result = a.Module.CompareTo(b.Module);
                        Debug.Assert(result != 0);
                        return result;
                    });

                    int baseRid = 0;
                    foreach (EcmaMethod inliner in sortedInliners)
                    {
                        int inlinerRid = MetadataTokens.GetRowNumber(inliner.Handle);
                        int ridDelta = inlinerRid - baseRid;
                        baseRid = inlinerRid;
                        Debug.Assert(ridDelta >= 0);
                        bool isForeignInliner = inliner.Module != _module;
                        sig.Append(new UnsignedConstant((uint)(ridDelta << 1 | (isForeignInliner ? 1 : 0))));
                        if (isForeignInliner)
                        {
                            sig.Append(new UnsignedConstant((uint)factory.ManifestMetadataTable.ModuleToIndex(inliner.Module)));
                        }
                    }
                }
                else
                {
                    // Format of the sequence:
                    // FOR CrossModuleInlineInfo format
                    //    Index with 2 flags field in lowest 2 bits to define the inlinee
                    //      - If flags & 1 == 0 then index is a MethodDef RID, and if the module is a composite image, a module index of the method follows
                    //      - If flags & 1 == 1, then index is an index into the ILBody import section
                    //      - If flags & 2 == 0 then what follows is:
                    //        - Inliner RID deltas - See definition below
                    //      - if flags & 2 == 2 then what follows is:
                    //        - count of delta encoded indices into the ILBody import section
                    //        - the sequence of delta encoded indices into the ILBody import section
                    //        - Inliner RID deltas - See definition below
                    //
                    //      Inliner RID deltas (for multi-module version bubble images (specified by the module having the READYTORUN_FLAG_MULTIMODULE_VERSION_BUBBLE flag set)
                    //        - a sequence of inliner RID deltas with flag in the lowest bit
                    //          - if flag is set, the inliner RID is followed by a module ID
                    //          - otherwise the module is the same as the module of the inlinee method
                    //
                    //      Inliner RID deltas (for single module version bubble images)
                    //        - a sequence of inliner RID deltas

                    bool crossModuleMultiModuleFormat = (factory.CompilationModuleGroup.GetReadyToRunFlags() & ReadyToRunFlags.READYTORUN_FLAG_MultiModuleVersionBubble) != 0;

                    Debug.Assert(_module == null);
                    bool isCrossModuleInlinee = !factory.CompilationModuleGroup.VersionsWithMethodBody(inlinee);
                    Debug.Assert(!isCrossModuleInlinee || factory.CompilationModuleGroup.CrossModuleInlineable(inlinee));

                    EcmaMethod[] sortedInliners = new EcmaMethod[inlineeWithInliners.Value.Count];
                    inlineeWithInliners.Value.CopyTo(sortedInliners);

                    sortedInliners.MergeSort((a, b) =>
                    {
                        if (a == b)
                            return 0;

                        bool isCrossModuleInlinerA = !factory.CompilationModuleGroup.VersionsWithMethodBody(a);
                        bool isCrossModuleInlinerB = !factory.CompilationModuleGroup.VersionsWithMethodBody(b);
                        if (isCrossModuleInlinerA != isCrossModuleInlinerB)
                        {
                            if (isCrossModuleInlinerA)
                                return -1;
                            else
                                return 1;
                        }

                        int result;
                        if (isCrossModuleInlinerA)
                        {
                            int indexA = _symbolNodeFactory.CheckILBodyFixupSignature(a).IndexFromBeginningOfArray;
                            int indexB = _symbolNodeFactory.CheckILBodyFixupSignature(b).IndexFromBeginningOfArray;
                            Debug.Assert(indexA != indexB);
                            result = indexA.CompareTo(indexB);
                        }
                        else
                        {
                            int aRid = MetadataTokens.GetRowNumber(a.Handle);
                            int bRid = MetadataTokens.GetRowNumber(b.Handle);
                            if (aRid < bRid)
                                return -1;
                            else if (aRid > bRid)
                                return 1;

                            result = a.Module.CompareTo(b.Module);
                        }
                        Debug.Assert(result != 0);
                        return result;
                    });

                    uint crossModuleInlinerCount = 0;
                    foreach (var method in sortedInliners)
                    {
                        if (factory.CompilationModuleGroup.VersionsWithMethodBody(method))
                            break;

                        Debug.Assert(factory.CompilationModuleGroup.CrossModuleInlineable(method));
                        crossModuleInlinerCount++;
                    }

                    uint encodedInlinee;
                    checked
                    {
                        uint indexOfInlinee;
                        if (isCrossModuleInlinee)
                        {
                            indexOfInlinee = (uint)_symbolNodeFactory.CheckILBodyFixupSignature(inlinee).IndexFromBeginningOfArray;
                        }
                        else
                        {
                            indexOfInlinee = (uint)MetadataTokens.GetRowNumber(ecmaInlinee.Handle);
                        }

                        encodedInlinee = indexOfInlinee << (int)ReadyToRunCrossModuleInlineFlags.CrossModuleInlinerIndexShift;

                        if (isCrossModuleInlinee)
                            encodedInlinee |= (uint)ReadyToRunCrossModuleInlineFlags.CrossModuleInlinee;

                        if (crossModuleInlinerCount > 0)
                            encodedInlinee |= (uint)ReadyToRunCrossModuleInlineFlags.HasCrossModuleInliners;

                        sig.Append(new UnsignedConstant(encodedInlinee));
                        if (crossModuleMultiModuleFormat && !isCrossModuleInlinee)
                            sig.Append(new UnsignedConstant((uint)factory.ManifestMetadataTable.ModuleToIndex(ecmaInlinee.Module)));

                        int inlinerIndex = 0;
                        if (crossModuleInlinerCount > 0)
                        {
                            sig.Append(new UnsignedConstant(crossModuleInlinerCount));
                            uint baseIndex = 0;
                            for (; inlinerIndex < crossModuleInlinerCount; inlinerIndex++)
                            {
                                var inliner = sortedInliners[inlinerIndex];

                                uint ilBodyIndex = (uint)_symbolNodeFactory.CheckILBodyFixupSignature(inliner).IndexFromBeginningOfArray;
                                uint ridDelta = ilBodyIndex - baseIndex;
                                sig.Append(new UnsignedConstant(ridDelta));
                            }
                        }

                        uint baseRid = 0;
                        for (; inlinerIndex < sortedInliners.Length; inlinerIndex++)
                        {
                            var inliner = sortedInliners[inlinerIndex];
                            uint inlinerRid = (uint)MetadataTokens.GetRowNumber(inliner.Handle);
                            uint ridDelta = inlinerRid - baseRid;
                            baseRid = inlinerRid;
                            bool isForeignInliner = inliner.Module != ecmaInlinee.Module;
                            Debug.Assert(!isForeignInliner || crossModuleMultiModuleFormat);

                            if (crossModuleMultiModuleFormat)
                            {
                                uint encodedRid = ridDelta << (int)ReadyToRunCrossModuleInlineFlags.InlinerRidShift;
                                if (isForeignInliner)
                                    encodedRid |= (uint)ReadyToRunCrossModuleInlineFlags.InlinerRidHasModule;

                                sig.Append(new UnsignedConstant(encodedRid));
                                if (isForeignInliner)
                                {
                                    sig.Append(new UnsignedConstant((uint)factory.ManifestMetadataTable.ModuleToIndex(inliner.Module)));
                                }
                            }
                            else
                            {
                                sig.Append(new UnsignedConstant(ridDelta));
                            }
                        }
                    }
                }

                hashtable.Append((uint)hashCode, section.Place(sig));
            }

            MemoryStream writerContent = new MemoryStream();
            writer.Save(writerContent);

            return new ObjectData(
                data: writerContent.ToArray(),
                relocs: null,
                alignment: 8,
                definedSymbols: new ISymbolDefinitionNode[] { this });
        }

        public override int ClassCode => -87382891;
    }
}