File: ObjectWriter\ProfileFileBuilder.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.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

using Internal.TypeSystem;

using ILCompiler.DependencyAnalysis;
using ILCompiler.DependencyAnalysis.ReadyToRun;
using ILCompiler.ObjectWriter;

namespace ILCompiler.PEWriter
{
    /// <summary>
    /// Helper class used to calculate code layout quality heuristics w.r.t. given call chain profile.
    /// </summary>
    public class ProfileFileBuilder
    {
        private enum CrossPageCall : byte
        {
            No,
            Yes,
            Unresolved,
        }

        private class CallInfo
        {
            public readonly MethodDesc Caller;
            public readonly OutputNode CallerNode;
            public readonly int CallerRVA;
            public readonly MethodDesc Callee;
            public readonly OutputNode CalleeNode;
            public readonly int CalleeRVA;
            public readonly int CallCount;
            public readonly CrossPageCall CallType;

            public CallInfo(MethodDesc caller, OutputNode callerNode, int callerRVA, MethodDesc callee, OutputNode calleeNode, int calleeRVA, int callCount, CrossPageCall callType)
            {
                Caller = caller;
                CallerNode = callerNode;
                CallerRVA = callerRVA;
                Callee = callee;
                CalleeNode = calleeNode;
                CalleeRVA = calleeRVA;
                CallCount = callCount;
                CallType = callType;
            }
        }

        private readonly OutputInfoBuilder _outputInfoBuilder;
        private readonly CallChainProfile _callChainProfile;
        private readonly TargetDetails _targetDetails;
        private readonly int _pageSize;

        private Dictionary<MethodDesc, ISymbolDefinitionNode> _symbolMethodMap;
        private List<CallInfo> _callInfo;

        public ProfileFileBuilder(OutputInfoBuilder outputInfoBuilder, CallChainProfile callChainProfile, TargetDetails targetDetails)
        {
            _outputInfoBuilder = outputInfoBuilder;
            _callChainProfile = callChainProfile;
            _targetDetails = targetDetails;
            _pageSize = _targetDetails.Architecture switch
            {
                TargetArchitecture.X86 => 0x00001000,
                TargetArchitecture.X64 => 0x00010000,
                TargetArchitecture.ARM => 0x00001000,
                TargetArchitecture.ARM64 => 0x00010000,
                _ => throw new NotImplementedException(_targetDetails.Architecture.ToString())
            };
        }

        public void SaveProfile(string profileFileName)
        {
            Console.WriteLine("Emitting profile file: {0}", profileFileName);

            CalculateCallInfo();
            using (StreamWriter writer = new StreamWriter(profileFileName))
            {
                writer.WriteLine("CHARACTERISTIC            | PAIR COUNT | CALL COUNT | PERCENTAGE");
                writer.WriteLine("----------------------------------------------------------------");
                int callCount = _callInfo.Sum(info => info.CallCount);
                double percentFactor = 100.0 / Math.Max(callCount, 1);
                writer.WriteLine("ENTRIES TOTAL             | {0,10} | {1,10} | {2,10:F2}", _callInfo.Count, callCount, callCount * percentFactor);
                int resolvedPairCount = _callInfo.Sum(info => info.CallType == CrossPageCall.Unresolved ? 0 : 1);
                int resolvedCallCount = _callInfo.Sum(info => info.CallType == CrossPageCall.Unresolved ? 0 : info.CallCount);
                writer.WriteLine("RESOLVED ENTRIES          | {0,10} | {1,10} | {2,10:F2}", resolvedPairCount, resolvedCallCount, resolvedCallCount * percentFactor);
                int unresolvedPairCount = _callInfo.Count - resolvedPairCount;
                int unresolvedCallCount = callCount - resolvedCallCount;
                writer.WriteLine("UNRESOLVED ENTRIES        | {0,10} | {1,10} | {2,10:F2}", unresolvedPairCount, unresolvedCallCount, unresolvedCallCount * percentFactor);
                int nearPairCount = _callInfo.Sum(info => info.CallType == CrossPageCall.No ? 1 : 0);
                int nearCallCount = _callInfo.Sum(info => info.CallType == CrossPageCall.No ? info.CallCount : 0);
                writer.WriteLine("NEAR (INTRA-PAGE) CALLS   | {0,10} | {1,10} | {2,10:F2}", nearPairCount, nearCallCount, nearCallCount * percentFactor);
                int farPairCount = _callInfo.Sum(info => info.CallType == CrossPageCall.Yes ? 1 : 0);
                int farCallCount = _callInfo.Sum(info => info.CallType == CrossPageCall.Yes ? info.CallCount : 0);
                writer.WriteLine("FAR (CROSS-PAGE) CALLS    | {0,10} | {1,10} | {2,10:F2}", farPairCount, farCallCount, farCallCount * percentFactor);

                writer.WriteLine();
                writer.WriteLine("CALLER RVA | CALLER LEN | CALLEE RVA | CALLEE LEN |      COUNT | FAR (CROSS-PAGE) CALLS (CALLER -> CALLEE)");
                writer.WriteLine("----------------------------------------------------------------------------------------------------------");
                DumpCallInfo(writer, _callInfo.Where(info => info.CallType == CrossPageCall.Yes).OrderByDescending(info => info.CallCount));

                writer.WriteLine();
                writer.WriteLine("CALLER RVA | CALLER LEN | CALLEE RVA | CALLEE LEN |      COUNT | NEAR (INTRA-PAGE) CALLS (CALLER -> CALLEE)");
                writer.WriteLine("-----------------------------------------------------------------------------------------------------------");
                DumpCallInfo(writer, _callInfo.Where(info => info.CallType == CrossPageCall.No).OrderByDescending(info => info.CallCount));
            }
        }

        private void DumpCallInfo(StreamWriter writer, IEnumerable<CallInfo> callInfos)
        {
            foreach (CallInfo callInfo in callInfos)
            {
                writer.Write($@"{callInfo.CallerRVA,10:X8} | ");
                writer.Write($@"{callInfo.CallerNode.Length,10:X8} | ");
                writer.Write($@"{callInfo.CalleeRVA,10:X8} | ");
                writer.Write($@"{callInfo.CalleeNode.Length,10:X8} | ");
                writer.Write($@"{callInfo.CallCount,10} | ");
                writer.WriteLine($@"{callInfo.Caller.ToString()} -> {callInfo.Callee.ToString()}");
            }
        }

        private void CalculateSymbolMethodMap()
        {
            if (_symbolMethodMap != null)
            {
                // Already calculated
                return;
            }
            _symbolMethodMap = new Dictionary<MethodDesc, ISymbolDefinitionNode>();
            foreach (KeyValuePair<ISymbolDefinitionNode, IMethodNode> kvpSymbolMethod in _outputInfoBuilder.MethodSymbolMap)
            {
                _symbolMethodMap.Add(kvpSymbolMethod.Value.Method, kvpSymbolMethod.Key);
            }
        }

        private void CalculateCallInfo()
        {
            if (_callInfo != null)
            {
                // Already calculated
                return;
            }

            CalculateSymbolMethodMap();

            _callInfo = new List<CallInfo>();
            foreach (KeyValuePair<MethodDesc, Dictionary<MethodDesc, int>> kvpCallerCalleeCount in _callChainProfile.ResolvedProfileData)
            {
                OutputNode callerNode = null;
                ulong callerRVA = 0;
                if (_symbolMethodMap.TryGetValue(kvpCallerCalleeCount.Key, out ISymbolDefinitionNode callerSymbol) &&
                    _outputInfoBuilder.NodeSymbolMap.TryGetValue(callerSymbol, out callerNode))
                {
                    callerRVA = _outputInfoBuilder.Sections[callerNode.SectionIndex].VirtualAddress + callerNode.Offset;
                }

                foreach (KeyValuePair<MethodDesc, int> kvpCalleeCount in kvpCallerCalleeCount.Value)
                {
                    OutputNode calleeNode = null;
                    ulong calleeRVA = 0;
                    if (_symbolMethodMap.TryGetValue(kvpCalleeCount.Key, out ISymbolDefinitionNode calleeSymbol) &&
                        _outputInfoBuilder.NodeSymbolMap.TryGetValue(calleeSymbol, out calleeNode))
                    {
                        calleeRVA = _outputInfoBuilder.Sections[calleeNode.SectionIndex].VirtualAddress + calleeNode.Offset;
                    }

                    int callerRVA32Bit = checked((int)callerRVA);
                    int calleeRVA32Bit = checked((int)calleeRVA);

                    _callInfo.Add(new CallInfo(
                        caller: kvpCallerCalleeCount.Key,
                        callerNode: callerNode,
                        callerRVA: callerRVA32Bit,
                        callee: kvpCalleeCount.Key,
                        calleeNode: calleeNode,
                        calleeRVA: calleeRVA32Bit,
                        callCount: kvpCalleeCount.Value,
                        callType: GetCallType(callerNode, callerRVA32Bit, calleeNode, calleeRVA32Bit)));
                }
            }
        }

        private CrossPageCall GetCallType(OutputNode caller, int callerRVA, OutputNode callee, int calleeRVA)
        {
            if (caller == null || callee == null)
            {
                return CrossPageCall.Unresolved;
            }
            int callerStartPage = callerRVA / _pageSize;
            int callerEndPage = (callerRVA + caller.Length - 1) / _pageSize;
            int calleePage = calleeRVA / _pageSize;

            if (callerStartPage == calleePage && callerEndPage == calleePage)
            {
                // The entire caller and the callee entrypoint are on the same page, no cross-page call penalty
                return CrossPageCall.No;
            }

            // Pessimistic estimate - we don't know where exactly the call is, we just know that it might cross a page.
            return CrossPageCall.Yes;
        }
    }
}