Skip to content

Commit

Permalink
Compare code gen (#129)
Browse files Browse the repository at this point in the history
* Compare code gen

Add a library to help with comparing code gen between the
  built-in system and the source generated approach.

* Append IL stub code gen when it is available.
  • Loading branch information
AaronRobinsonMSFT authored Oct 16, 2020
1 parent c89fc41 commit 76354fe
Show file tree
Hide file tree
Showing 5 changed files with 670 additions and 5 deletions.
17 changes: 12 additions & 5 deletions DllImportGenerator/DllImportGenerator.sln
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,14 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "TestAssets", "TestAssets",
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NativeExports", "TestAssets\NativeExports\NativeExports.csproj", "{32FDA079-0E9F-4A36-ADA5-6593B67A54AC}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DllImportGenerator.IntegrationTests", "DllImportGenerator.IntegrationTests\DllImportGenerator.IntegrationTests.csproj", "{162C204A-ED59-4EF3-A5FA-E58CC06FAB4D}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DllImportGenerator.IntegrationTests", "DllImportGenerator.IntegrationTests\DllImportGenerator.IntegrationTests.csproj", "{162C204A-ED59-4EF3-A5FA-E58CC06FAB4D}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tools", "Tools", "{69D56AC9-232B-4E76-B6C1-33A7B06B6855}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PInvokeDump", "Tools\PInvokeDump\PInvokeDump.csproj", "{6FD4AF19-0CAA-413C-A2BD-C888AA2E8CFB}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Benchmarking", "Tools\Benchmarking\Benchmarking.csproj", "{927F1081-FFE6-4897-9030-D9023F7EE604}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand All @@ -47,21 +49,26 @@ Global
{32FDA079-0E9F-4A36-ADA5-6593B67A54AC}.Debug|Any CPU.Build.0 = Debug|Any CPU
{32FDA079-0E9F-4A36-ADA5-6593B67A54AC}.Release|Any CPU.ActiveCfg = Release|Any CPU
{32FDA079-0E9F-4A36-ADA5-6593B67A54AC}.Release|Any CPU.Build.0 = Release|Any CPU
{6FD4AF19-0CAA-413C-A2BD-C888AA2E8CFB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{6FD4AF19-0CAA-413C-A2BD-C888AA2E8CFB}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6FD4AF19-0CAA-413C-A2BD-C888AA2E8CFB}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6FD4AF19-0CAA-413C-A2BD-C888AA2E8CFB}.Release|Any CPU.Build.0 = Release|Any CPU
{162C204A-ED59-4EF3-A5FA-E58CC06FAB4D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{162C204A-ED59-4EF3-A5FA-E58CC06FAB4D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{162C204A-ED59-4EF3-A5FA-E58CC06FAB4D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{162C204A-ED59-4EF3-A5FA-E58CC06FAB4D}.Release|Any CPU.Build.0 = Release|Any CPU
{6FD4AF19-0CAA-413C-A2BD-C888AA2E8CFB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{6FD4AF19-0CAA-413C-A2BD-C888AA2E8CFB}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6FD4AF19-0CAA-413C-A2BD-C888AA2E8CFB}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6FD4AF19-0CAA-413C-A2BD-C888AA2E8CFB}.Release|Any CPU.Build.0 = Release|Any CPU
{927F1081-FFE6-4897-9030-D9023F7EE604}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{927F1081-FFE6-4897-9030-D9023F7EE604}.Debug|Any CPU.Build.0 = Debug|Any CPU
{927F1081-FFE6-4897-9030-D9023F7EE604}.Release|Any CPU.ActiveCfg = Release|Any CPU
{927F1081-FFE6-4897-9030-D9023F7EE604}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{32FDA079-0E9F-4A36-ADA5-6593B67A54AC} = {2CFB9A7A-4AAF-4B6A-8CC8-540F64C3B45F}
{6FD4AF19-0CAA-413C-A2BD-C888AA2E8CFB} = {69D56AC9-232B-4E76-B6C1-33A7B06B6855}
{927F1081-FFE6-4897-9030-D9023F7EE604} = {69D56AC9-232B-4E76-B6C1-33A7B06B6855}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {5344B739-3A02-402A-8777-0D54DEC4F3BA}
Expand Down
12 changes: 12 additions & 0 deletions DllImportGenerator/Tools/Benchmarking/Benchmarking.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Iced" Version="1.8.0" />
</ItemGroup>

</Project>
293 changes: 293 additions & 0 deletions DllImportGenerator/Tools/Benchmarking/CodeGenEventListener.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,293 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.Tracing;
using System.IO;
using Iced.Intel;

using Decoder = Iced.Intel.Decoder;

namespace Benchmarking
{
internal class CodeGenEventListener : EventListener
{
// See src/coreclr/src/vm/ClrEtwAll.man
private const int JitKeyword = 0x10;
private const int InteropKeyword = 0x2000;

// Event IDs
private const int ILStubGeneratedId = 88;
private const int VerboseMethodLoadId = 143;

private readonly Dictionary<ulong, PInvokeInstance> pinvokeInstances = new Dictionary<ulong, PInvokeInstance>();

/// <summary>
/// Event fired when code is generated for a method.
/// </summary>
public event EventHandler<MethodCodeGen> NewMethodCodeGen;

protected override void OnEventSourceCreated(EventSource eventSource)
{
if (eventSource.Name.Equals("Microsoft-Windows-DotNETRuntime"))
{
this.EnableEvents(
eventSource,
EventLevel.Verbose,
(EventKeywords)(JitKeyword | InteropKeyword));
}
}

protected override void OnEventWritten(EventWrittenEventArgs eventData)
{
switch (eventData.EventId)
{
case ILStubGeneratedId: this.OnILStubGenerated(eventData); break;
case VerboseMethodLoadId: this.OnVerboseMethodLoad(eventData); break;
}
}

private void OnNewMethodCodeGen(MethodCodeGen mcg)
{
var h = this.NewMethodCodeGen;
if (h is not null)
{
h(this, mcg);
}
}

private void OnILStubGenerated(EventWrittenEventArgs eventData)
{
Debug.Assert(eventData.EventId == ILStubGeneratedId);

ulong methodId = 0;
string fqClassName = string.Empty;
string methodName = string.Empty;
string ilCode = string.Empty;
uint metadataToken = 0;

// See https://docs.microsoft.com/dotnet/framework/performance/interop-etw-events
for (int i = 0; i < eventData.Payload.Count; i++)
{
object payload = eventData.Payload[i];
string payloadName = eventData.PayloadNames[i];

if (payloadName == "StubMethodID")
{
methodId = (ulong)payload;
}
else if (payloadName == "ManagedInteropMethodNamespace")
{
fqClassName = (string)payload;
}
else if (payloadName == "ManagedInteropMethodName")
{
methodName = (string)payload;
}
else if (payloadName == "ManagedInteropMethodToken")
{
metadataToken = (uint)payload;
}
else if (payloadName == "StubMethodILCode")
{
ilCode = (string)payload;
}
}

Debug.Assert(methodId != 0);
pinvokeInstances.Add(methodId, new PInvokeInstance()
{
MetadataToken = metadataToken,
FullyQualifiedClassName = fqClassName,
MethodName = methodName,
ILCode = ilCode,
});;
}

private void OnVerboseMethodLoad(EventWrittenEventArgs eventData)
{
Debug.Assert(eventData.EventId == VerboseMethodLoadId);

using var generatedCode = new StringWriter();

ulong methodId = 0;
ulong startAddress = 0;
uint size = 0;
string namespaceName = "?";
string methodName = "?";
string methodSignature = "?";
string ilCode = string.Empty;
uint flags = 0;
uint metadataToken = 0;

// See https://docs.microsoft.com/dotnet/framework/performance/method-etw-events
for (int i = 0; i < eventData.Payload.Count; i++)
{
object payload = eventData.Payload[i];
string payloadName = eventData.PayloadNames[i];

if (payloadName == "MethodID")
{
methodId = (ulong)payload;
}
else if (payloadName == "MethodStartAddress")
{
startAddress = (ulong)payload;
}
else if (payloadName == "MethodSize")
{
size = (uint)payload;
}
else if (payloadName == "MethodNamespace")
{
namespaceName = (string)payload;
}
else if (payloadName == "MethodName")
{
methodName = (string)payload;
}
else if (payloadName == "MethodSignature")
{
methodSignature = (string)payload;
}
else if (payloadName == "MethodFlags")
{
flags = (uint)payload;
}
else if (payloadName == "MethodToken")
{
metadataToken = (uint)payload;
}
}

bool isILStub = this.pinvokeInstances.TryGetValue(methodId, out PInvokeInstance pin);
if (isILStub)
{
Debug.Assert(metadataToken == 0);
metadataToken = pin.MetadataToken;
namespaceName = pin.FullyQualifiedClassName;
methodName = pin.MethodName;
ilCode = pin.ILCode;
}

string flagString = "";

if ((flags & 0x8) != 0)
{
flagString += " jitted";
}
else
{
flagString += " prejitted";
}

uint optLevel = ((flags >> 7) & 0x7);

switch (optLevel)
{
case 1: flagString += " minopts"; break;
case 2: flagString += " fullopts"; break;
case 3: flagString += " tier0"; break;
case 4: flagString += " tier1"; break;
case 5: flagString += " tier1-OSR"; break;
default: flagString += " unknown codegen"; break;
}

generatedCode.WriteLine($"{namespaceName}{Type.Delimiter}{methodName}{(isILStub ? " (ILStub)" : string.Empty)}");
generatedCode.WriteLine(methodSignature);
generatedCode.WriteLine($"0x{startAddress:X16} {size:D6}{flagString,-20}");

unsafe
{
var codeReader = new MemoryCodeReader((byte*)startAddress, size);
var decoder = Decoder.Create(IntPtr.Size * 8, codeReader);
decoder.IP = startAddress;
ulong endRip = startAddress + (ulong)size;
const int HEXBYTES_COLUMN_BYTE_LENGTH = 10;
var instructions = new InstructionList();

while (decoder.IP < endRip)
{
// The method allocates an uninitialized element at the end of the list and
// returns a reference to it which is initialized by Decode().
decoder.Decode(out instructions.AllocUninitializedElement());
}

// Formatters: Masm*, Nasm*, Gas* (AT&T) and Intel* (XED)
var formatter = new NasmFormatter();
formatter.Options.DigitSeparator = "`";
formatter.Options.FirstOperandCharIndex = 10;
var output = new StringOutput();
// Use InstructionList's ref iterator (C# 7.3) to prevent copying 32 bytes every iteration
foreach (ref var instr in instructions)
{
// Don't use instr.ToString(), it allocates more, uses masm syntax and default options
// Console.WriteLine(instr);
formatter.Format(instr, output);
int instrLen = instr.Length;
int byteBaseIndex = (int)(instr.IP - startAddress);
for (int i = 0; i < instrLen; i++)
{
generatedCode.Write(codeReader[byteBaseIndex + i].ToString("X2"));
}

int missingBytes = HEXBYTES_COLUMN_BYTE_LENGTH - instrLen;
for (int i = 0; i < missingBytes; i++)
{
generatedCode.Write(" ");
}

generatedCode.Write(' ');
generatedCode.WriteLine(output.ToStringAndReset());
}
}

this.OnNewMethodCodeGen(new MethodCodeGen()
{
MetadataToken = metadataToken,
IsILStub = isILStub,
FullyQualifiedClassName = namespaceName,
MethodName = methodName,
GeneratedCode = generatedCode.ToString(),
GeneratedCodeSize = size,
ILCode = ilCode,
});
}

private record PInvokeInstance
{
public uint MetadataToken { get; init; }
public string FullyQualifiedClassName { get; init; }
public string MethodName { get; init; }
public string ILCode { get; init; }
}

private unsafe class MemoryCodeReader : CodeReader
{
private readonly byte* baseAddress;
private readonly uint length;
private uint next = 0;

public MemoryCodeReader(byte* baseAddress, uint length)
{
Debug.Assert(baseAddress is not null);
this.baseAddress = baseAddress;
this.length = length;
}

public bool CanReadByte() => next < this.length;

public override int ReadByte()
{
if (next >= length)
{
return -1;
}

return baseAddress[next++];
}

public byte this[int i] => baseAddress[i];
}

}
}
Loading

0 comments on commit 76354fe

Please sign in to comment.