Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
115 changes: 92 additions & 23 deletions src/coreclr/tools/aot/ILCompiler.Diagnostics/PerfMapWriter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using System.Linq;
using System.Security.Cryptography;

using Internal.ReadyToRunDiagnosticsConstants;
using Internal.TypeSystem;

namespace ILCompiler.Diagnostics
Expand All @@ -16,14 +17,8 @@ public class PerfMapWriter
public const int LegacyCrossgen1FormatVersion = 0;

public const int CurrentFormatVersion = 1;

public enum PseudoRVA : uint
{
OutputGuid = 0xFFFFFFFF,
TargetOS = 0xFFFFFFFE,
TargetArchitecture = 0xFFFFFFFD,
FormatVersion = 0xFFFFFFFC,
}

const int HeaderEntriesPseudoLength = 0;

private TextWriter _writer;

Expand All @@ -32,7 +27,7 @@ private PerfMapWriter(TextWriter writer)
_writer = writer;
}

public static void Write(string perfMapFileName, int perfMapFormatVersion, IEnumerable<MethodInfo> methods, IEnumerable<AssemblyInfo> inputAssemblies, TargetOS targetOS, TargetArchitecture targetArch)
public static void Write(string perfMapFileName, int perfMapFormatVersion, IEnumerable<MethodInfo> methods, IEnumerable<AssemblyInfo> inputAssemblies, TargetDetails details)
{
if (perfMapFormatVersion > CurrentFormatVersion)
{
Expand All @@ -41,22 +36,10 @@ public static void Write(string perfMapFileName, int perfMapFormatVersion, IEnum

using (TextWriter writer = new StreamWriter(perfMapFileName))
{
IEnumerable<AssemblyInfo> orderedInputs = inputAssemblies.OrderBy(asm => asm.Name, StringComparer.OrdinalIgnoreCase);

PerfMapWriter perfMapWriter = new PerfMapWriter(writer);

List<byte> inputHash = new List<byte>();
foreach (AssemblyInfo inputAssembly in orderedInputs)
{
inputHash.AddRange(inputAssembly.Mvid.ToByteArray());
}
inputHash.Add((byte)targetOS);
inputHash.Add((byte)targetArch);
Guid outputGuid = new Guid(MD5.HashData(inputHash.ToArray()));
perfMapWriter.WriteLine(outputGuid.ToString(), (uint)PseudoRVA.OutputGuid, 0);
perfMapWriter.WriteLine(targetOS.ToString(), (uint)PseudoRVA.TargetOS, 0);
perfMapWriter.WriteLine(targetArch.ToString(), (uint)PseudoRVA.TargetArchitecture, 0);
perfMapWriter.WriteLine(CurrentFormatVersion.ToString(), (uint)PseudoRVA.FormatVersion, 0);
byte[] signature = PerfMapV1SignatureHelper(inputAssemblies, details);
WritePerfMapV1Header(inputAssemblies, details, perfMapWriter);

foreach (MethodInfo methodInfo in methods)
{
Expand All @@ -72,6 +55,92 @@ public static void Write(string perfMapFileName, int perfMapFormatVersion, IEnum
}
}

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.CoreRT => PerfMapAbiToken.Default,
TargetAbi.CoreRTArmel => 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,
_ => 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}");
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

namespace Internal.ReadyToRunDiagnosticsConstants;

public enum PerfMapPseudoRVAToken : uint
{
OutputSignature = 0xFFFFFFFF,
FormatVersion = 0xFFFFFFFE,
TargetOS = 0xFFFFFFFD,
TargetArchitecture = 0xFFFFFFFC,
TargetABI = 0xFFFFFFFB,
}

public enum PerfMapArchitectureToken : uint
{
Unknown = 0,
ARM = 1,
ARM64 = 2,
X64 = 3,
X86 = 4,
}

public enum PerfMapOSToken : uint
{
Unknown = 0,
Windows = 1,
Linux = 2,
OSX = 3,
FreeBSD = 4,
NetBSD = 5,
SunOS = 6,
}

public enum PerfMapAbiToken : uint
{
Unknown = 0,
Default = 1,
Armel = 2,
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ namespace ILCompiler.DependencyAnalysis
internal class ReadyToRunObjectWriter
{
/// <summary>
/// Nodefactory for which ObjectWriter is instantiated for.
/// Nodefactory for which ObjectWriter is instantiated for.
/// </summary>
private readonly NodeFactory _nodeFactory;

Expand Down Expand Up @@ -234,6 +234,7 @@ public void EmitPortableExecutable()
peIdProvider);

NativeDebugDirectoryEntryNode nativeDebugDirectoryEntryNode = null;
PerfMapDebugDirectoryEntryNode perfMapDebugDirectoryEntryNode = null;
ISymbolDefinitionNode firstImportThunk = null;
ISymbolDefinitionNode lastImportThunk = null;
ObjectNode lastWrittenObjectNode = null;
Expand Down Expand Up @@ -261,6 +262,13 @@ public void EmitPortableExecutable()
nativeDebugDirectoryEntryNode = nddeNode;
}

if (node is PerfMapDebugDirectoryEntryNode pmdeNode)
{
// There should be only one PerfMapDebugDirectoryEntryNode.
Debug.Assert(perfMapDebugDirectoryEntryNode is null);
perfMapDebugDirectoryEntryNode = pmdeNode;
}

if (node is ImportThunk importThunkNode)
{
Debug.Assert(firstImportThunk == null || lastWrittenObjectNode is ImportThunk,
Expand Down Expand Up @@ -305,14 +313,22 @@ public void EmitPortableExecutable()
{
r2rPeBuilder.AddSymbolForRange(_nodeFactory.DelayLoadMethodCallThunks, firstImportThunk, lastImportThunk);
}


if (_nodeFactory.Win32ResourcesNode != null)
{
Debug.Assert(_nodeFactory.Win32ResourcesNode.Size != 0);
r2rPeBuilder.SetWin32Resources(_nodeFactory.Win32ResourcesNode, _nodeFactory.Win32ResourcesNode.Size);
}

if (_outputInfoBuilder != null)
{
foreach (string inputFile in _inputFiles)
{
_outputInfoBuilder.AddInputModule(_nodeFactory.TypeSystemContext.GetModuleFromPath(inputFile));
}
}

using (var peStream = File.Create(_objectFilePath))
{
r2rPeBuilder.Write(peStream, timeDateStamp);
Expand All @@ -322,26 +338,36 @@ public void EmitPortableExecutable()
_mapFileBuilder.SetFileSize(peStream.Length);
}

// Compute MD5 hash of the output image and store that in the native DebugDirectory entry
using (var md5Hash = MD5.Create())
if (nativeDebugDirectoryEntryNode is not null)
{
peStream.Seek(0, SeekOrigin.Begin);
byte[] hash = md5Hash.ComputeHash(peStream);
byte[] rsdsEntry = nativeDebugDirectoryEntryNode.GenerateRSDSEntryData(hash);
Debug.Assert(_generatePdbFile);
// Compute MD5 hash of the output image and store that in the native DebugDirectory entry
using (var md5Hash = MD5.Create())
{
peStream.Seek(0, SeekOrigin.Begin);
byte[] hash = md5Hash.ComputeHash(peStream);
byte[] rsdsEntry = nativeDebugDirectoryEntryNode.GenerateRSDSEntryData(hash);

int offsetToUpdate = r2rPeBuilder.GetSymbolFilePosition(nativeDebugDirectoryEntryNode);
peStream.Seek(offsetToUpdate, SeekOrigin.Begin);
peStream.Write(rsdsEntry);
}
}

if (perfMapDebugDirectoryEntryNode is not null)
{
Debug.Assert(_generatePerfMapFile && _outputInfoBuilder is not null && _outputInfoBuilder.EnumerateInputAssemblies().Any());
byte[] perfmapSig = PerfMapWriter.PerfMapV1SignatureHelper(_outputInfoBuilder.EnumerateInputAssemblies(), _nodeFactory.Target);
byte[] perfMapEntry = perfMapDebugDirectoryEntryNode.GeneratePerfMapEntryData(perfmapSig, _perfMapFormatVersion);

int offsetToUpdate = r2rPeBuilder.GetSymbolFilePosition(nativeDebugDirectoryEntryNode);
int offsetToUpdate = r2rPeBuilder.GetSymbolFilePosition(perfMapDebugDirectoryEntryNode);
peStream.Seek(offsetToUpdate, SeekOrigin.Begin);
peStream.Write(rsdsEntry);
peStream.Write(perfMapEntry);
}
}

if (_outputInfoBuilder != null)
{
foreach (string inputFile in _inputFiles)
{
_outputInfoBuilder.AddInputModule(_nodeFactory.TypeSystemContext.GetModuleFromPath(inputFile));
}

r2rPeBuilder.AddSections(_outputInfoBuilder);

if (_generateMapFile)
Expand Down Expand Up @@ -374,7 +400,7 @@ public void EmitPortableExecutable()
{
path = Path.GetDirectoryName(_objectFilePath);
}
_symbolFileBuilder.SavePerfMap(path, _perfMapFormatVersion, _objectFilePath, _nodeFactory.Target.OperatingSystem, _nodeFactory.Target.Architecture);
_symbolFileBuilder.SavePerfMap(path, _perfMapFormatVersion, _objectFilePath, _nodeFactory.Target);
}

if (_profileFileBuilder != null)
Expand Down
Loading