diff --git a/src/coreclr/src/tools/crossgen2/ILCompiler.ReadyToRun/CodeGen/ReadyToRunObjectWriter.cs b/src/coreclr/src/tools/crossgen2/ILCompiler.ReadyToRun/CodeGen/ReadyToRunObjectWriter.cs index 2da9f2400985cf..c38cc8b6fa989d 100644 --- a/src/coreclr/src/tools/crossgen2/ILCompiler.ReadyToRun/CodeGen/ReadyToRunObjectWriter.cs +++ b/src/coreclr/src/tools/crossgen2/ILCompiler.ReadyToRun/CodeGen/ReadyToRunObjectWriter.cs @@ -50,9 +50,9 @@ internal class ReadyToRunObjectWriter private readonly IEnumerable _nodes; /// - /// True when the executable generator should output a map file. + /// Set to non-null when the executable generator should output a map file. /// - private readonly bool _generateMapFile; + private readonly MapFileBuilder _mapFileBuilder; #if DEBUG private struct NodeInfo @@ -78,31 +78,22 @@ public ReadyToRunObjectWriter(string objectFilePath, EcmaModule componentModule, _componentModule = componentModule; _nodes = nodes; _nodeFactory = factory; - _generateMapFile = generateMapFile; + + if (generateMapFile) + { + _mapFileBuilder = new MapFileBuilder(); + } } public void EmitPortableExecutable() { bool succeeded = false; - FileStream mapFileStream = null; - TextWriter mapFile = null; - try { - if (_generateMapFile) - { - string mapFileName = Path.ChangeExtension(_objectFilePath, ".map"); - mapFileStream = new FileStream(mapFileName, FileMode.Create, FileAccess.Write); - mapFile = new StreamWriter(mapFileStream); - } - Stopwatch stopwatch = new Stopwatch(); stopwatch.Start(); - if (mapFile != null) - mapFile.WriteLine($@"R2R object emission started: {DateTime.Now}"); - PEHeaderBuilder headerBuilder; int timeDateStamp; ISymbolNode r2rHeaderExportSymbol; @@ -167,7 +158,7 @@ public void EmitPortableExecutable() string name = null; - if (mapFile != null) + if (_mapFileBuilder != null) { name = depNode.GetType().ToString(); int firstGeneric = name.IndexOf('['); @@ -182,7 +173,7 @@ public void EmitPortableExecutable() } } - EmitObjectData(r2rPeBuilder, nodeContents, nodeIndex, name, node.Section, mapFile); + EmitObjectData(r2rPeBuilder, nodeContents, nodeIndex, name, node.Section, _mapFileBuilder); } if (!_nodeFactory.CompilationModuleGroup.IsCompositeBuildMode || _componentModule != null) @@ -201,6 +192,11 @@ public void EmitPortableExecutable() { r2rPeBuilder.Write(peStream, timeDateStamp); + if (_mapFileBuilder != null) + { + _mapFileBuilder.SetFileSize(peStream.Length); + } + // Compute MD5 hash of the output image and store that in the native DebugDirectory entry using (var md5Hash = MD5.Create()) { @@ -214,25 +210,18 @@ public void EmitPortableExecutable() } } - if (mapFile != null) + if (_mapFileBuilder != null) { - mapFile.WriteLine($@"R2R object emission finished: {DateTime.Now}, {stopwatch.ElapsedMilliseconds} msecs"); - mapFile.Flush(); - mapFileStream.Flush(); + r2rPeBuilder.AddSections(_mapFileBuilder); + + string mapFileName = Path.ChangeExtension(_objectFilePath, ".map"); + _mapFileBuilder.Save(mapFileName); } succeeded = true; } finally { - if (mapFile != null) - { - mapFile.Dispose(); - } - if (mapFileStream != null) - { - mapFileStream.Dispose(); - } if (!succeeded) { // If there was an exception while generating the OBJ file, make sure we don't leave the unfinished @@ -264,7 +253,7 @@ public void EmitPortableExecutable() /// Textual representation of the ObjecData blob in the map file /// Section to emit the blob into /// Map file output stream - private void EmitObjectData(R2RPEBuilder r2rPeBuilder, ObjectData data, int nodeIndex, string name, ObjectNodeSection section, TextWriter mapFile) + private void EmitObjectData(R2RPEBuilder r2rPeBuilder, ObjectData data, int nodeIndex, string name, ObjectNodeSection section, MapFileBuilder mapFileBuilder) { #if DEBUG for (int symbolIndex = 0; symbolIndex < data.DefinedSymbols.Length; symbolIndex++) @@ -283,7 +272,7 @@ private void EmitObjectData(R2RPEBuilder r2rPeBuilder, ObjectData data, int node } #endif - r2rPeBuilder.AddObjectData(data, section, name, mapFile); + r2rPeBuilder.AddObjectData(data, section, name, mapFileBuilder); } public static void EmitObject(string objectFilePath, EcmaModule componentModule, IEnumerable nodes, NodeFactory factory, bool generateMapFile) diff --git a/src/coreclr/src/tools/crossgen2/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/MethodWithGCInfo.cs b/src/coreclr/src/tools/crossgen2/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/MethodWithGCInfo.cs index 718c9c43d4ae09..2d01e8698710f1 100644 --- a/src/coreclr/src/tools/crossgen2/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/MethodWithGCInfo.cs +++ b/src/coreclr/src/tools/crossgen2/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/MethodWithGCInfo.cs @@ -303,5 +303,6 @@ public void InitializeInliningInfo(MethodDesc[] inlinedMethods) public int Offset => 0; public override bool IsShareable => throw new NotImplementedException(); + public override bool ShouldSkipEmittingObjectNode(NodeFactory factory) => IsEmpty; } } diff --git a/src/coreclr/src/tools/crossgen2/ILCompiler.ReadyToRun/ILCompiler.ReadyToRun.csproj b/src/coreclr/src/tools/crossgen2/ILCompiler.ReadyToRun/ILCompiler.ReadyToRun.csproj index 2374f684987e7a..1912b13fca84d4 100644 --- a/src/coreclr/src/tools/crossgen2/ILCompiler.ReadyToRun/ILCompiler.ReadyToRun.csproj +++ b/src/coreclr/src/tools/crossgen2/ILCompiler.ReadyToRun/ILCompiler.ReadyToRun.csproj @@ -95,6 +95,7 @@ + diff --git a/src/coreclr/src/tools/crossgen2/ILCompiler.ReadyToRun/ObjectWriter/MapFileBuilder.cs b/src/coreclr/src/tools/crossgen2/ILCompiler.ReadyToRun/ObjectWriter/MapFileBuilder.cs new file mode 100644 index 00000000000000..291e3973ff90ab --- /dev/null +++ b/src/coreclr/src/tools/crossgen2/ILCompiler.ReadyToRun/ObjectWriter/MapFileBuilder.cs @@ -0,0 +1,280 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Data; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.IO; +using System.Linq; +using System.Reflection.Metadata; +using System.Text; +using System.Xml; + +namespace ILCompiler.PEWriter +{ + /// + /// Base class for symbols and nodes in the map file implements common logic + /// for section / offset ordering. + /// + public class MapFileItem + { + public class Comparer : IComparer + { + public readonly static Comparer Instance = new Comparer(); + + public int Compare([AllowNull] MapFileItem x, [AllowNull] MapFileItem y) + { + return (x.SectionIndex != y.SectionIndex ? x.SectionIndex.CompareTo(y.SectionIndex) : x.Offset.CompareTo(y.Offset)); + } + } + + /// + /// Item section index + /// + public readonly int SectionIndex; + + /// + /// Offset relative to section beginning + /// + public readonly int Offset; + + /// + /// Item name + /// + public readonly string Name; + + public MapFileItem(int sectionIndex, int offset, string name) + { + SectionIndex = sectionIndex; + Offset = offset; + Name = name; + } + } + + /// + /// This class represents a single node (contiguous block of data) in the output R2R PE file. + /// + public class MapFileNode : MapFileItem + { + /// + /// Node length (number of bytes). This doesn't include any external alignment + /// applied when concatenating the nodes to form sections. + /// + public readonly int Length; + + public MapFileNode(int sectionIndex, int offset, int length, string name) + : base(sectionIndex, offset, name) + { + Length = length; + } + } + + /// + /// Symbol is a "pointer" into the PE file. Most (but not all) symbols correspond to + /// node beginnings (most nodes have a "start symbol" representing the beginning + /// of the node). + /// + public class MapFileSymbol : MapFileItem + { + public MapFileSymbol(int sectionIndex, int offset, string name) + : base(sectionIndex, offset, name) + { + } + } + + /// + /// Helper class used to collect information to be output into the map file. + /// + public class MapFileBuilder + { + /// + /// Number of first characters of the section name to retain in the symbol & node map. + /// + private const int SectionNameHeadLength = 7; + + /// + /// Statistic information for a single node type. + /// + private class Statistics + { + public readonly string Name; + + public int Count; + public int Length; + + public Statistics(string name) + { + Name = name; + } + + public void AddNode(MapFileNode node) + { + Debug.Assert(Name == node.Name); + Count++; + Length += node.Length; + } + } + + private readonly List _nodes; + private readonly List _symbols; + private readonly List
_sections; + + private long _fileSize; + + public MapFileBuilder() + { + _nodes = new List(); + _symbols = new List(); + _sections = new List
(); + } + + public void AddNode(MapFileNode node) + { + _nodes.Add(node); + } + + public void AddSymbol(MapFileSymbol symbol) + { + _symbols.Add(symbol); + } + + public void AddSection(Section section) + { + _sections.Add(section); + } + + public void SetFileSize(long fileSize) + { + _fileSize = fileSize; + } + + public void Save(string mapFileName) + { + Console.WriteLine("Emitting map file: {0}", mapFileName); + + _nodes.Sort(MapFileItem.Comparer.Instance); + _symbols.Sort(MapFileItem.Comparer.Instance); + + using (StreamWriter mapWriter = new StreamWriter(mapFileName)) + { + WriteHeader(mapWriter); + WriteStatistics(mapWriter); + WriteSections(mapWriter); + WriteMap(mapWriter); + } + } + + private void WriteHeader(StreamWriter writer) + { + WriteTitle(writer, "Summary Info"); + + writer.WriteLine($"Output file size: {_fileSize,10}"); + writer.WriteLine($"Section count: {_sections.Count,10}"); + writer.WriteLine($"Node count: {_nodes.Count,10}"); + writer.WriteLine($"Symbol count: {_symbols.Count,10}"); + } + + private void WriteStatistics(StreamWriter writer) + { + List nodeTypeStats = new List(); + Dictionary statsNameIndex = new Dictionary(); + foreach (MapFileNode node in _nodes) + { + if (!statsNameIndex.TryGetValue(node.Name, out int statsIndex)) + { + statsIndex = nodeTypeStats.Count; + nodeTypeStats.Add(new Statistics(node.Name)); + statsNameIndex.Add(node.Name, statsIndex); + } + nodeTypeStats[statsIndex].AddNode(node); + } + nodeTypeStats.Sort((a, b) => b.Length.CompareTo(a.Length)); + + WriteTitle(writer, "Node Type Statistics"); + WriteTitle(writer, " LENGTH | %FILE | AVERAGE | COUNT | NODETYPE"); + foreach (Statistics nodeStats in nodeTypeStats) + { + writer.Write($"{nodeStats.Length,10} | "); + writer.Write($"{(nodeStats.Length * 100.0 / _fileSize),7:F3} | "); + writer.Write($"{(nodeStats.Length / (double)nodeStats.Count),10:F1} | "); + writer.Write($"{nodeStats.Count,6} | "); + writer.WriteLine(nodeStats.Name); + } + } + + private void WriteSections(StreamWriter writer) + { + WriteTitle(writer, "Section Map"); + WriteTitle(writer, "INDEX | FILEOFFSET | RVA | END_RVA | LENGTH | NAME"); + for (int sectionIndex = 0; sectionIndex < _sections.Count; sectionIndex++) + { + Section section = _sections[sectionIndex]; + writer.Write($"{sectionIndex,5} | "); + writer.Write($"0x{section.FilePosWhenPlaced:X8} | "); + writer.Write($"0x{section.RVAWhenPlaced:X8} | "); + writer.Write($"0x{(section.RVAWhenPlaced + section.Content.Count):X8} | "); + writer.Write($"0x{section.Content.Count:X8} | "); + writer.WriteLine(section.Name); + } + } + + private void WriteMap(StreamWriter writer) + { + WriteTitle(writer, "Node & Symbol Map"); + WriteTitle(writer, "RVA | LENGTH | SECTION | SYMBOL (NODE)"); + + int nodeIndex = 0; + int symbolIndex = 0; + + while (nodeIndex < _nodes.Count || symbolIndex < _symbols.Count) + { + if (nodeIndex >= _nodes.Count || symbolIndex < _symbols.Count && MapFileItem.Comparer.Instance.Compare(_symbols[symbolIndex], _nodes[nodeIndex]) < 0) + { + // No more nodes or next symbol is below next node - emit symbol + MapFileSymbol symbol = _symbols[symbolIndex++]; + Section section = _sections[symbol.SectionIndex]; + writer.Write($"0x{symbol.Offset + section.RVAWhenPlaced:X8} | "); + writer.Write(" | "); // + writer.Write($"{GetNameHead(section),-SectionNameHeadLength} | "); + writer.WriteLine(symbol.Name); + } + else + { + // Emit node and optionally symbol + MapFileNode node = _nodes[nodeIndex++]; + Section section = _sections[node.SectionIndex]; + + writer.Write($"0x{node.Offset + section.RVAWhenPlaced:X8} | "); + writer.Write($"0x{node.Length:X6} | "); + writer.Write($"{GetNameHead(section),-SectionNameHeadLength} | "); + if (symbolIndex < _symbols.Count && MapFileItem.Comparer.Instance.Compare(node, _symbols[symbolIndex]) == 0) + { + MapFileSymbol symbol = _symbols[symbolIndex++]; + writer.Write($"{symbol.Name}"); + } + writer.WriteLine($" ({node.Name})"); + } + } + } + + private static string GetNameHead(Section section) + { + string sectionNameHead = section.Name; + if (sectionNameHead.Length > SectionNameHeadLength) + { + sectionNameHead = sectionNameHead.Substring(0, SectionNameHeadLength); + } + return sectionNameHead; + } + + private void WriteTitle(StreamWriter writer, string title) + { + writer.WriteLine(); + writer.WriteLine(title); + writer.WriteLine(new string('-', title.Length)); + } + } +} diff --git a/src/coreclr/src/tools/crossgen2/ILCompiler.ReadyToRun/ObjectWriter/R2RPEBuilder.cs b/src/coreclr/src/tools/crossgen2/ILCompiler.ReadyToRun/ObjectWriter/R2RPEBuilder.cs index ac63ae303a1c09..bb648b9af99253 100644 --- a/src/coreclr/src/tools/crossgen2/ILCompiler.ReadyToRun/ObjectWriter/R2RPEBuilder.cs +++ b/src/coreclr/src/tools/crossgen2/ILCompiler.ReadyToRun/ObjectWriter/R2RPEBuilder.cs @@ -223,8 +223,8 @@ public void SetWin32Resources(ISymbolNode symbol, int resourcesSize) /// Object data to emit /// Target section /// Textual name of the object data for diagnostic purposese - /// Optional map file to output the data item to - public void AddObjectData(ObjectNode.ObjectData objectData, ObjectNodeSection section, string name, TextWriter mapFile) + /// Optional map file builder to output the data item to + public void AddObjectData(ObjectNode.ObjectData objectData, ObjectNodeSection section, string name, MapFileBuilder mapFileBuilder) { if (_written) { @@ -248,7 +248,7 @@ public void AddObjectData(ObjectNode.ObjectData objectData, ObjectNodeSection se throw new NotImplementedException(); } - _sectionBuilder.AddObjectData(objectData, targetSectionIndex, name, mapFile); + _sectionBuilder.AddObjectData(objectData, targetSectionIndex, name, mapFileBuilder); } public int GetSymbolFilePosition(ISymbolNode symbol) @@ -276,6 +276,15 @@ public void Write(Stream outputStream, int timeDateStamp) _written = true; } + /// + /// Fill in map builder section table. + /// + /// Map file builder to set up + public void AddSections(MapFileBuilder mapFileBuilder) + { + _sectionBuilder.AddSections(mapFileBuilder); + } + /// /// PE header constants copied from System.Reflection.Metadata where they are /// sadly mostly internal or private. diff --git a/src/coreclr/src/tools/crossgen2/ILCompiler.ReadyToRun/ObjectWriter/SectionBuilder.cs b/src/coreclr/src/tools/crossgen2/ILCompiler.ReadyToRun/ObjectWriter/SectionBuilder.cs index 904bea5ec0397c..ead3a9fe1879cd 100644 --- a/src/coreclr/src/tools/crossgen2/ILCompiler.ReadyToRun/ObjectWriter/SectionBuilder.cs +++ b/src/coreclr/src/tools/crossgen2/ILCompiler.ReadyToRun/ObjectWriter/SectionBuilder.cs @@ -442,8 +442,8 @@ private NameMangler GetNameMangler() /// Block to add /// Section index /// Node name to emit in the map file - /// Optional map file to emit - public void AddObjectData(ObjectNode.ObjectData objectData, int sectionIndex, string name, TextWriter mapFile) + /// Optional map file to emit + public void AddObjectData(ObjectNode.ObjectData objectData, int sectionIndex, string name, MapFileBuilder mapFileBuilder) { Section section = _sections[sectionIndex]; @@ -480,9 +480,9 @@ public void AddObjectData(ObjectNode.ObjectData objectData, int sectionIndex, st } } - if (mapFile != null) + if (mapFileBuilder != null) { - mapFile.WriteLine($@"S{sectionIndex}+0x{alignedOffset:X4}..{(alignedOffset + objectData.Data.Length):X4}: {objectData.Data.Length:X4} * {name}"); + mapFileBuilder.AddNode(new MapFileNode(sectionIndex, alignedOffset, objectData.Data.Length, name)); } section.Content.WriteBytes(objectData.Data); @@ -491,12 +491,12 @@ public void AddObjectData(ObjectNode.ObjectData objectData, int sectionIndex, st { foreach (ISymbolDefinitionNode symbol in objectData.DefinedSymbols) { - if (mapFile != null) + if (mapFileBuilder != null) { Utf8StringBuilder sb = new Utf8StringBuilder(); symbol.AppendMangledName(GetNameMangler(), sb); int sectionRelativeOffset = alignedOffset + symbol.Offset; - mapFile.WriteLine($@" +0x{sectionRelativeOffset:X4}: {sb.ToString()}"); + mapFileBuilder.AddSymbol(new MapFileSymbol(sectionIndex, sectionRelativeOffset, sb.ToString())); } _symbolMap.Add(symbol, new SymbolTarget( sectionIndex: sectionIndex, @@ -530,6 +530,14 @@ public IEnumerable GetSections() return sectionList; } + public void AddSections(MapFileBuilder mapFileBuilder) + { + foreach (Section section in _sections) + { + mapFileBuilder.AddSection(section); + } + } + /// /// Traverse blocks within a single section and use them to calculate final layout /// of the given section.