Skip to content

Commit

Permalink
Add generation of structured object file dumps (#73913)
Browse files Browse the repository at this point in the history
The compiler can currently emit an XML file with everything that was emitted into the output object file. This works and it's easily human readable. But the captured information is not very structured and cannot be used to generate aggregate information like 'this assembly contributed X bytes".

This adds another dumper format similar to what we do for MIBC and others: it's ECMA-335 based and uses ldtoken/ldstr/ldc to encode the data.

This can be used to build better tools. There's currently none but ILDASM, but it would be good to have it for 7.0. Good hackathon project.
  • Loading branch information
MichalStrehovsky authored Aug 15, 2022
1 parent 06339dc commit 00f34fb
Show file tree
Hide file tree
Showing 7 changed files with 253 additions and 60 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,7 @@ The .NET Foundation licenses this file to you under the MIT license.
<IlcArg Condition="$(NativeDebugSymbols) == 'true'" Include="-g" />
<IlcArg Condition="$(IlcDwarfVersion) == '5'" Include="--gdwarf-5" />
<IlcArg Condition="$(IlcGenerateMapFile) == 'true'" Include="--map:$(NativeIntermediateOutputPath)%(ManagedBinary.Filename).map.xml" />
<IlcArg Condition="$(IlcGenerateMstatFile) == 'true'" Include="--mstat:$(NativeIntermediateOutputPath)%(ManagedBinary.Filename).mstat" />
<IlcArg Condition="$(IlcGenerateDgmlFile) == 'true'" Include="--dgmllog:$(NativeIntermediateOutputPath)%(ManagedBinary.Filename).codegen.dgml.xml" />
<IlcArg Condition="$(IlcGenerateDgmlFile) == 'true'" Include="--scandgmllog:$(NativeIntermediateOutputPath)%(ManagedBinary.Filename).scan.dgml.xml" />
<IlcArg Include="@(RdXmlFile->'--rdxml:%(FullPath)')" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ public class MethodExceptionHandlingInfoNode : ObjectNode, ISymbolDefinitionNode
private readonly MethodDesc _owningMethod;
private readonly ObjectData _data;

public MethodDesc Method => _owningMethod;

public MethodExceptionHandlingInfoNode(MethodDesc owningMethod, ObjectData data)
{
_owningMethod = owningMethod;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
// 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.IO;
using System.Reflection.Metadata;
using System.Reflection.Metadata.Ecma335;

using Internal.Text;
using Internal.TypeSystem;

using ILCompiler.DependencyAnalysis;

using ObjectData = ILCompiler.DependencyAnalysis.ObjectNode.ObjectData;
using AssemblyName = System.Reflection.AssemblyName;
using System.Collections.Generic;
using static ILCompiler.DependencyAnalysis.ObjectNode;

namespace ILCompiler
{
public class MstatObjectDumper : ObjectDumper
{
private const int VersionMajor = 1;
private const int VersionMinor = 0;

private readonly string _fileName;
private readonly TypeSystemMetadataEmitter _emitter;

private readonly InstructionEncoder _types = new InstructionEncoder(new BlobBuilder());

private Dictionary<MethodDesc, (string MangledName, int Size, int GcInfoSize)> _methods = new();
private Dictionary<MethodDesc, int> _methodEhInfo = new();
private Dictionary<string, int> _blobs = new();

private Utf8StringBuilder _utf8StringBuilder = new Utf8StringBuilder();

public MstatObjectDumper(string fileName, TypeSystemContext context)
{
_fileName = fileName;
var asmName = new AssemblyName(Path.GetFileName(fileName));
asmName.Version = new Version(VersionMajor, VersionMinor);
_emitter = new TypeSystemMetadataEmitter(asmName, context);
_emitter.AllowUseOfAddGlobalMethod();
}

internal override void Begin()
{
}

protected override void DumpObjectNode(NameMangler mangler, ObjectNode node, ObjectData objectData)
{
string mangledName = null;
if (node is ISymbolNode symbol)
{
_utf8StringBuilder.Clear();
symbol.AppendMangledName(mangler, _utf8StringBuilder);
mangledName = _utf8StringBuilder.ToString();
}

switch (node)
{
case EETypeNode eeType:
SerializeSimpleEntry(_types, eeType.Type, mangledName, objectData);
break;
case IMethodBodyNode methodBody:
var codeInfo = (INodeWithCodeInfo)node;
_methods.Add(methodBody.Method, (mangledName, objectData.Data.Length, codeInfo.GCInfo.Length));
break;
case MethodExceptionHandlingInfoNode ehInfoNode:
_methodEhInfo.Add(ehInfoNode.Method, objectData.Data.Length);
break;
default:
string nodeName = GetObjectNodeName(node);
if (!_blobs.TryGetValue(nodeName, out int size))
size = 0;
_blobs[nodeName] = size + objectData.Data.Length;
break;
}
}

private void SerializeSimpleEntry(InstructionEncoder encoder, TypeSystemEntity entity, string mangledName, ObjectData blob)
{
encoder.OpCode(ILOpCode.Ldtoken);
encoder.Token(_emitter.EmitMetadataHandleForTypeSystemEntity(entity));
encoder.LoadString(_emitter.GetUserStringHandle(mangledName));
encoder.LoadConstantI4(blob.Data.Length);
}

internal override void End()
{
var methods = new InstructionEncoder(new BlobBuilder());
foreach (var m in _methods)
{
methods.OpCode(ILOpCode.Ldtoken);
methods.Token(_emitter.EmitMetadataHandleForTypeSystemEntity(m.Key));
methods.LoadString(_emitter.GetUserStringHandle(m.Value.MangledName));
methods.LoadConstantI4(m.Value.Size);
methods.LoadConstantI4(m.Value.GcInfoSize);
methods.LoadConstantI4(_methodEhInfo.GetValueOrDefault(m.Key));
}

var blobs = new InstructionEncoder(new BlobBuilder());
foreach (var b in _blobs)
{
blobs.LoadString(_emitter.GetUserStringHandle(b.Key));
blobs.LoadConstantI4(b.Value);
}

_emitter.AddGlobalMethod("Methods", methods, 0);
_emitter.AddGlobalMethod("Types", _types, 0);
_emitter.AddGlobalMethod("Blobs", blobs, 0);

using (var fs = File.OpenWrite(_fileName))
_emitter.SerializeToStream(fs);
}
}
}
93 changes: 35 additions & 58 deletions src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectDumper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.IO;
using System.Security.Cryptography;
using System.Xml;
using System.Collections.Generic;

using Internal.Text;

Expand All @@ -14,31 +12,14 @@

namespace ILCompiler
{
public class ObjectDumper : IObjectDumper
public abstract class ObjectDumper : IObjectDumper
{
private readonly string _fileName;
private SHA256 _sha256;
private XmlWriter _writer;
internal abstract void Begin();
internal abstract void End();
void IObjectDumper.DumpObjectNode(NameMangler mangler, ObjectNode node, ObjectData objectData) => DumpObjectNode(mangler, node, objectData);
protected abstract void DumpObjectNode(NameMangler mangler, ObjectNode node, ObjectData objectData);

public ObjectDumper(string fileName)
{
_fileName = fileName;
}

internal void Begin()
{
var settings = new XmlWriterSettings
{
CloseOutput = true,
Indent = true,
};

_sha256 = SHA256.Create();
_writer = XmlWriter.Create(File.CreateText(_fileName), settings);
_writer.WriteStartElement("ObjectNodes");
}

private static string GetObjectNodeName(ObjectNode node)
protected static string GetObjectNodeName(ObjectNode node)
{
string name = node.GetType().Name;

Expand All @@ -54,46 +35,42 @@ private static string GetObjectNodeName(ObjectNode node)
return name;
}

void IObjectDumper.DumpObjectNode(NameMangler mangler, ObjectNode node, ObjectData objectData)
public static ObjectDumper Compose(IEnumerable<ObjectDumper> dumpers)
{
string name = null;

_writer.WriteStartElement(GetObjectNodeName(node));

var symbolNode = node as ISymbolNode;
if (symbolNode != null)
{
Utf8StringBuilder sb = new Utf8StringBuilder();
symbolNode.AppendMangledName(mangler, sb);
name = sb.ToString();
_writer.WriteAttributeString("Name", name);
}
var dumpersList = new ArrayBuilder<ObjectDumper>();

_writer.WriteAttributeString("Length", objectData.Data.Length.ToStringInvariant());
_writer.WriteAttributeString("Hash", HashData(objectData.Data));
_writer.WriteEndElement();
foreach (var dumper in dumpers)
dumpersList.Add(dumper);

var nodeWithCodeInfo = node as INodeWithCodeInfo;
if (nodeWithCodeInfo != null)
return dumpersList.Count switch
{
_writer.WriteStartElement("GCInfo");
_writer.WriteAttributeString("Name", name);
_writer.WriteAttributeString("Length", nodeWithCodeInfo.GCInfo.Length.ToStringInvariant());
_writer.WriteAttributeString("Hash", HashData(nodeWithCodeInfo.GCInfo));
_writer.WriteEndElement();
}
0 => null,
1 => dumpersList[0],
_ => new ComposedObjectDumper(dumpersList.ToArray()),
};
}

private string HashData(byte[] data)
private class ComposedObjectDumper : ObjectDumper
{
return BitConverter.ToString(_sha256.ComputeHash(data)).Replace("-", "").ToLower();
}
private readonly ObjectDumper[] _dumpers;

internal void End()
{
_writer.WriteEndElement();
_writer.Dispose();
_writer = null;
public ComposedObjectDumper(ObjectDumper[] dumpers) => _dumpers = dumpers;

protected override void DumpObjectNode(NameMangler mangler, ObjectNode node, ObjectData objectData)
{
foreach (var d in _dumpers)
d.DumpObjectNode(mangler, node, objectData);
}
internal override void Begin()
{
foreach (var d in _dumpers)
d.Begin();
}
internal override void End()
{
foreach (var d in _dumpers)
d.End();
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
// 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.IO;
using System.Security.Cryptography;
using System.Xml;

using Internal.Text;

using ILCompiler.DependencyAnalysis;

using ObjectData = ILCompiler.DependencyAnalysis.ObjectNode.ObjectData;

namespace ILCompiler
{
public class XmlObjectDumper : ObjectDumper
{
private readonly string _fileName;
private SHA256 _sha256;
private XmlWriter _writer;

public XmlObjectDumper(string fileName)
{
_fileName = fileName;
}

internal override void Begin()
{
var settings = new XmlWriterSettings
{
CloseOutput = true,
Indent = true,
};

_sha256 = SHA256.Create();
_writer = XmlWriter.Create(File.CreateText(_fileName), settings);
_writer.WriteStartElement("ObjectNodes");
}

protected override void DumpObjectNode(NameMangler mangler, ObjectNode node, ObjectData objectData)
{
string name = null;

_writer.WriteStartElement(GetObjectNodeName(node));

var symbolNode = node as ISymbolNode;
if (symbolNode != null)
{
Utf8StringBuilder sb = new Utf8StringBuilder();
symbolNode.AppendMangledName(mangler, sb);
name = sb.ToString();
_writer.WriteAttributeString("Name", name);
}

_writer.WriteAttributeString("Length", objectData.Data.Length.ToStringInvariant());
_writer.WriteAttributeString("Hash", HashData(objectData.Data));
_writer.WriteEndElement();

var nodeWithCodeInfo = node as INodeWithCodeInfo;
if (nodeWithCodeInfo != null)
{
_writer.WriteStartElement("GCInfo");
_writer.WriteAttributeString("Name", name);
_writer.WriteAttributeString("Length", nodeWithCodeInfo.GCInfo.Length.ToStringInvariant());
_writer.WriteAttributeString("Hash", HashData(nodeWithCodeInfo.GCInfo));
_writer.WriteEndElement();
}
}

private string HashData(byte[] data)
{
return BitConverter.ToString(_sha256.ComputeHash(data)).Replace("-", "").ToLower();
}

internal override void End()
{
_writer.WriteEndElement();
_writer.Dispose();
_writer = null;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,9 @@
<Compile Include="..\..\Common\TypeSystem\CodeGen\NativeStructType.CodeGen.cs">
<Link>TypeSystem\CodeGen\NativeStructType.CodeGen.cs</Link>
</Compile>
<Compile Include="..\..\Common\TypeSystem\MetadataEmitter\TypeSystemMetadataEmitter.cs">
<Link>TypeSystem\MetadataEmitter\TypeSystemMetadataEmitter.cs</Link>
</Compile>
<Compile Include="..\..\Common\Internal\Runtime\GCDescEncoder.cs">
<Link>Common\GCDescEncoder.cs</Link>
</Compile>
Expand Down Expand Up @@ -423,6 +426,7 @@
<Compile Include="Compiler\IInliningPolicy.cs" />
<Compile Include="Compiler\ManifestResourceBlockingPolicy.cs" />
<Compile Include="Compiler\MethodImportationErrorProvider.cs" />
<Compile Include="Compiler\MstatObjectDumper.cs" />
<Compile Include="Compiler\NoMetadataBlockingPolicy.cs" />
<Compile Include="Compiler\DependencyAnalysis\FrozenObjectNode.cs" />
<Compile Include="Compiler\DependencyAnalysis\GCStaticsPreInitDataNode.cs" />
Expand Down Expand Up @@ -593,6 +597,7 @@
<Compile Include="Compiler\Logging\DocumentationSignatureParser.cs" />
<Compile Include="Compiler\Logging\MessageContainer.cs" />
<Compile Include="Compiler\Logging\MessageOrigin.cs" />
<Compile Include="Compiler\XmlObjectDumper.cs" />
<Compile Include="IL\ILImporter.Scanner.cs" />
<Compile Include="IL\Stubs\PInvokeILProvider.cs" />
<Compile Include="IL\Stubs\StartupCode\AppContextInitializerMethod.cs" />
Expand Down
Loading

0 comments on commit 00f34fb

Please sign in to comment.