File: PerfMapWriter.cs
Web Access
Project: src\src\runtime\src\coreclr\tools\aot\ILCompiler.Diagnostics\ILCompiler.Diagnostics.csproj (ILCompiler.Diagnostics)
// 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.Security.Cryptography;

using Internal.ReadyToRunDiagnosticsConstants;
using Internal.TypeSystem;

namespace ILCompiler.Diagnostics
{
    public class PerfMapWriter
    {
        public const int LegacyCrossgen1FormatVersion = 0;

        public const int CurrentFormatVersion = 1;

        const int HeaderEntriesPseudoLength = 0;

        private TextWriter _writer;

        private PerfMapWriter(TextWriter writer)
        {
            _writer = writer;
        }

        public static void Write(string perfMapFileName, int perfMapFormatVersion, IEnumerable<MethodInfo> methods, IEnumerable<AssemblyInfo> inputAssemblies, TargetDetails details)
        {
            if (perfMapFormatVersion > CurrentFormatVersion)
            {
                throw new NotSupportedException(perfMapFormatVersion.ToString());
            }

            using (TextWriter writer = new StreamWriter(perfMapFileName))
            {

                PerfMapWriter perfMapWriter = new PerfMapWriter(writer);
                byte[] signature = PerfMapV1SignatureHelper(inputAssemblies, details);
                WritePerfMapV1Header(inputAssemblies, details, perfMapWriter);

                foreach (MethodInfo methodInfo in methods)
                {
                    if (methodInfo.HotRVA != 0 && methodInfo.HotLength != 0)
                    {
                        perfMapWriter.WriteLine(methodInfo.Name, methodInfo.HotRVA, methodInfo.HotLength);
                    }
                    if (methodInfo.ColdRVA != 0 && methodInfo.ColdLength != 0)
                    {
                        perfMapWriter.WriteLine(methodInfo.Name, methodInfo.ColdRVA, methodInfo.ColdLength);
                    }
                }
            }
        }

        private static void WritePerfMapV1Header(IEnumerable<AssemblyInfo> inputAssemblies, TargetDetails details, PerfMapWriter perfMapWriter)
        {
            byte[] signature = PerfMapV1SignatureHelper(inputAssemblies, details);

            // Make sure these get emitted in this order, other tools in the ecosystem like the symbol uploader and PerfView rely on this.
            // In particular, the order of it. Append only.
            string signatureFormatted = Convert.ToHexString(signature);

            PerfmapTokensForTarget targetTokens = TranslateTargetDetailsToPerfmapConstants(details);

            perfMapWriter.WriteLine(signatureFormatted, (uint)PerfMapPseudoRVAToken.OutputSignature, HeaderEntriesPseudoLength);
            perfMapWriter.WriteLine(CurrentFormatVersion.ToString(), (uint)PerfMapPseudoRVAToken.FormatVersion, HeaderEntriesPseudoLength);
            perfMapWriter.WriteLine(((uint)targetTokens.OperatingSystem).ToString(), (uint)PerfMapPseudoRVAToken.TargetOS, HeaderEntriesPseudoLength);
            perfMapWriter.WriteLine(((uint)targetTokens.Architecture).ToString(), (uint)PerfMapPseudoRVAToken.TargetArchitecture, HeaderEntriesPseudoLength);
            perfMapWriter.WriteLine(((uint)targetTokens.Abi).ToString(), (uint)PerfMapPseudoRVAToken.TargetABI, HeaderEntriesPseudoLength);
        }

        public static byte[] PerfMapV1SignatureHelper(IEnumerable<AssemblyInfo> inputAssemblies, TargetDetails details)
        {
            IEnumerable<AssemblyInfo> orderedInputs = inputAssemblies.OrderBy(asm => asm.Name, StringComparer.OrdinalIgnoreCase);
            List<byte> inputHash = new List<byte>();
            foreach (AssemblyInfo inputAssembly in orderedInputs)
            {
                inputHash.AddRange(inputAssembly.Mvid.ToByteArray());
            }

            PerfmapTokensForTarget targetTokens = TranslateTargetDetailsToPerfmapConstants(details);

            byte[] buffer = new byte[12];
            if (!BitConverter.TryWriteBytes(buffer.AsSpan(0, sizeof(uint)), (uint)targetTokens.OperatingSystem)
                || !BitConverter.TryWriteBytes(buffer.AsSpan(4, sizeof(uint)), (uint)targetTokens.Architecture)
                || !BitConverter.TryWriteBytes(buffer.AsSpan(8, sizeof(uint)), (uint)targetTokens.Abi))
            {
                throw new InvalidOperationException();
            }

            if (!BitConverter.IsLittleEndian)
            {
                buffer.AsSpan(0, sizeof(uint)).Reverse();
                buffer.AsSpan(4, sizeof(uint)).Reverse();
                buffer.AsSpan(8, sizeof(uint)).Reverse();
            }

            inputHash.AddRange(buffer);
            byte[] hash = MD5.HashData(inputHash.ToArray());

            return hash;
        }

        internal record struct PerfmapTokensForTarget(PerfMapOSToken OperatingSystem, PerfMapArchitectureToken Architecture, PerfMapAbiToken Abi);

        private static PerfmapTokensForTarget TranslateTargetDetailsToPerfmapConstants(TargetDetails details)
        {
            PerfMapOSToken osToken = details.OperatingSystem switch
            {
                TargetOS.Unknown => PerfMapOSToken.Unknown,
                TargetOS.Windows => PerfMapOSToken.Windows,
                TargetOS.Linux => PerfMapOSToken.Linux,
                TargetOS.OSX => PerfMapOSToken.OSX,
                TargetOS.FreeBSD => PerfMapOSToken.FreeBSD,
                TargetOS.NetBSD => PerfMapOSToken.NetBSD,
                TargetOS.SunOS => PerfMapOSToken.SunOS,
                _ => throw new NotImplementedException(details.OperatingSystem.ToString())
            };

            PerfMapAbiToken abiToken = details.Abi switch
            {
                TargetAbi.Unknown => PerfMapAbiToken.Unknown,
                TargetAbi.NativeAot => PerfMapAbiToken.Default,
                TargetAbi.NativeAotArmel => PerfMapAbiToken.Armel,
                _ => throw new NotImplementedException(details.Abi.ToString())
            };

            PerfMapArchitectureToken archToken = details.Architecture switch
            {
                TargetArchitecture.Unknown => PerfMapArchitectureToken.Unknown,
                TargetArchitecture.ARM => PerfMapArchitectureToken.ARM,
                TargetArchitecture.ARM64 => PerfMapArchitectureToken.ARM64,
                TargetArchitecture.X64 => PerfMapArchitectureToken.X64,
                TargetArchitecture.X86 => PerfMapArchitectureToken.X86,
                TargetArchitecture.RiscV64 => PerfMapArchitectureToken.RiscV64,
                TargetArchitecture.LoongArch64 => PerfMapArchitectureToken.LoongArch64,
                _ => throw new NotImplementedException(details.Architecture.ToString())
            };

            return new PerfmapTokensForTarget(osToken, archToken, abiToken);
        }

        private void WriteLine(string methodName, uint rva, uint length)
        {
            _writer.WriteLine($@"{rva:X8} {length:X2} {methodName}");
        }
    }
}