Skip to content

Instantly share code, notes, and snippets.

@Warpten
Last active October 15, 2018 13:21
Show Gist options
  • Save Warpten/c8f0d809f346684401df89ab85139cd9 to your computer and use it in GitHub Desktop.
Save Warpten/c8f0d809f346684401df89ab85139cd9 to your computer and use it in GitHub Desktop.
diff --git a/src/BenchmarkDotNet.Disassembler.x64/Program.cs b/src/BenchmarkDotNet.Disassembler.x64/Program.cs
index c5b71018..5ccb567b 100644
--- a/src/BenchmarkDotNet.Disassembler.x64/Program.cs
+++ b/src/BenchmarkDotNet.Disassembler.x64/Program.cs
@@ -20,7 +20,7 @@ namespace BenchmarkDotNet.Disassembler
static readonly string[] CallSeparator = { "call" };
static readonly Dictionary<string, string[]> SourceFileCache = new Dictionary<string, string[]>();
- // the goals of the existence of this process:
+ // the goals of the existence of this process:
// 1. attach to benchmarked process
// 2. disassemble the code
// 3. save it to xml file
@@ -71,7 +71,7 @@ namespace BenchmarkDotNet.Disassembler
var disasembledMethods = Disassemble(settings, runtime, state);
// we don't want to export the disassembler entry point method which is just an artificial method added to get generic types working
- var methodsToExport = disasembledMethods.Where(method =>
+ var methodsToExport = disasembledMethods.Where(method =>
disasembledMethods.Count == 1 // if there is only one method we want to return it (most probably benchmark got inlined)
|| !method.Name.Contains(DisassemblerConstants.DisassemblerEntryMethodName)).ToArray();
@@ -103,8 +103,10 @@ namespace BenchmarkDotNet.Disassembler
state.Todo.Enqueue(
new MethodInfo(
// benchmarks in BenchmarkDotNet are always parameterless, so check by name is enough as of today
- typeWithBenchmark.Methods.Single(method => method.IsPublic && method.Name == settings.MethodName && method.GetFullSignature().EndsWith("()")),
- 0));
+ typeWithBenchmark.Methods.Single(method => method.IsPublic && method.Name == settings.MethodName && method.GetFullSignature().EndsWith("()")),
+ 0,
+ // benchmarks can be in any namespace, null is just a magic value to disable exclusion checks
+ null));
while (state.Todo.Count != 0)
{
@@ -113,8 +115,18 @@ namespace BenchmarkDotNet.Disassembler
if (!state.HandledMethods.Add(new MethodId(method.Method.MetadataToken, method.Method.Type.MetadataToken))) // add it now to avoid StackOverflow for recursive methods
continue; // already handled
- if(settings.RecursiveDepth >= method.Depth)
+ if (settings.RecursiveDepth >= method.Depth)
+ {
+ var disassembledMethod = DisassembleMethod(method, state, settings);
+
+ if (settings.ExcludedNamespaces.Length > 0 && method.Namespace != null)
+ {
+ if (settings.ExcludedNamespaces.Contains(method.Namespace))
+ continue;
+ }
+
- result.Add(DisassembleMethod(method, state, settings));
+ result.Add(disassembledMethod);
+ }
}
return result;
@@ -141,7 +153,7 @@ namespace BenchmarkDotNet.Disassembler
if (method.NativeCode == ulong.MaxValue || method.ILOffsetMap == null)
{
// we have no asm, so we are going to try to get the methods from IL
- EnqueueAllCallsFromIL(state, ilInstructions, methodInfo.Depth);
+ EnqueueAllCallsFromIL(state, ilInstructions, methodInfo.Depth, settings);
if (method.NativeCode == ulong.MaxValue)
if (method.IsAbstract) return CreateEmpty(method, "Abstract method");
@@ -177,7 +189,7 @@ namespace BenchmarkDotNet.Disassembler
// but only the first ones and the last ones if PrintPrologAndEpilog == false
bool methodWithoutBody = method.ILOffsetMap.All(map => map.ILOffset < 0); // sth like [NoInlining] void Sample() { }
int startIndex = settings.PrintPrologAndEpilog || methodWithoutBody
- ? 0
+ ? 0
: mapByStartAddress.TakeWhile(map => map.ILOffset < 0).Count();
int endIndex = settings.PrintPrologAndEpilog || methodWithoutBody
? mapByStartAddress.Length
@@ -219,7 +231,7 @@ namespace BenchmarkDotNet.Disassembler
}
// after we read the asm we try to find some other methods which were not direct calls in asm
- EnqueueAllCallsFromIL(state, ilInstructions, methodInfo.Depth);
+ EnqueueAllCallsFromIL(state, ilInstructions, methodInfo.Depth, settings);
return new DisassembledMethod
{
@@ -232,7 +244,7 @@ namespace BenchmarkDotNet.Disassembler
static Map CreateMap(IEnumerable<Code> instructions) => new Map { Instructions = instructions.ToList() };
static IEnumerable<IL> GetIL(ICollection<Instruction> instructions)
- => instructions.Select(instruction
+ => instructions.Select(instruction
=> new IL
{
TextRepresentation = instruction.ToString(),
@@ -327,16 +339,57 @@ namespace BenchmarkDotNet.Disassembler
return null; // in case of call which is just a jump within the method
if (!state.HandledMethods.Contains(new MethodId(method.MetadataToken, method.Type.MetadataToken)))
- state.Todo.Enqueue(new MethodInfo(method, depth + 1));
-
+ {
+ TryGetNamespace(method.GetFullSignature(), method.Type.Name, out var @namespace);
+ state.Todo.Enqueue(new MethodInfo(method, depth + 1, @namespace));
+ }
return method.GetFullSignature();
}
+ static bool TryGetNamespace(string fullSignature, string declaringTypeName, out string @namespace)
+ {
+ var parenthesisOffset = fullSignature.IndexOf('(');
+ if (parenthesisOffset == -1)
+ {
+ @namespace = null;
+ return false;
+ }
+
+ // The return type is currently not part of a method's signature but let's be cautious in case
+ // this changes for whatever the reason may be.
+ var spaceOffset = fullSignature.IndexOf(' ');
+ if (spaceOffset == -1 || spaceOffset > parenthesisOffset)
+ spaceOffset = 0;
+
+ var fullyQualifiedName = fullSignature.Substring(spaceOffset, parenthesisOffset - spaceOffset);
+ if (declaringTypeName != null)
+ {
+ var declaringName = fullyQualifiedName.IndexOf("." + declaringTypeName);
+ if (declaringName == -1)
+ {
+ @namespace = null;
+ return false;
+ }
+
+ @namespace = fullyQualifiedName.Substring(0, declaringName);
+ return true;
+ }
+ else
+ {
+ // This is harder and may become inaccurate...
+ // In System.Foo.Bar, "System.Foo" would be the namespace.
+ // In System.Foo.EnclosingType.Bar, we would calculate "System.Foo.EnclosingType", while it should be "System.Foo".
+ }
+
+ @namespace = null;
+ return false;
+ }
+
static bool TryGetHexAdress(string textRepresentation, out ulong address)
{
// it's always "something call something addr`ess something"
// 00007ffe`16fb04e4 e897fbffff call 00007ffe`16fb0080 // static or instance method call
- // 000007fe`979171fb e800261b5e call mscorlib_ni+0x499800 (000007fe`f5ac9800) // managed implementation in mscorlib
+ // 000007fe`979171fb e800261b5e call mscorlib_ni+0x499800 (000007fe`f5ac9800) // managed implementation in mscorlib
// 000007fe`979f666f 41ff13 call qword ptr [r11] ds:000007fe`978e0050=000007fe978ed260
// 00007ffe`16fc0503 e81820615f call clr+0x2520 (00007ffe`765d2520) // native implementation in Clr
var rightPart = textRepresentation
@@ -358,24 +411,24 @@ namespace BenchmarkDotNet.Disassembler
/// <summary>
/// for some calls we can not parse the method address from asm, so we just get it from IL
/// </summary>
- static void EnqueueAllCallsFromIL(State state, ICollection<Instruction> ilInstructions, int depth)
+ static void EnqueueAllCallsFromIL(State state, ICollection<Instruction> ilInstructions, int depth, Settings settings)
{
// let's try to enqueue all method calls that we can find in IL and were not printed yet
foreach (var callInstruction in ilInstructions.Where(instruction => instruction.Operand is MethodReference)) // todo: handle CallSite
{
var methodReference = (MethodReference)callInstruction.Operand;
- var declaringType =
+ var declaringType =
methodReference.DeclaringType.IsNested
? state.Runtime.Heap.GetTypeByName(methodReference.DeclaringType.FullName.Replace('/', '+')) // nested types contains `/` instead of `+` in the name..
: state.Runtime.Heap.GetTypeByName(methodReference.DeclaringType.FullName);
- if(declaringType == null)
+ if (declaringType == null)
continue; // todo: eliminate Cecil vs ClrMD differences in searching by name
var calledMethod = GetMethod(methodReference, declaringType);
if (calledMethod != null && !state.HandledMethods.Contains(new MethodId(calledMethod.MetadataToken, declaringType.MetadataToken)))
- state.Todo.Enqueue(new MethodInfo(calledMethod, depth + 1));
+ state.Todo.Enqueue(new MethodInfo(calledMethod, depth + 1, methodReference.DeclaringType.Namespace));
}
}
@@ -389,20 +442,20 @@ namespace BenchmarkDotNet.Disassembler
if (methodsWithSameToken.Length > 1 && methodReference.MetadataToken.ToUInt32() != default(UInt32))
{
var compiled = methodsWithSameToken.Where(method => method.CompilationType != MethodCompilationType.None).ToArray();
-
+
if (compiled.Length != 1) // very rare case where two different methods have the same metadata token ;)
return compiled.Single(method => method.Name == methodReference.Name);;
-
+
// usually one is NGened, the other one is not compiled (looks like a ClrMD bug to me)
return compiled[0];
}
// comparing metadata tokens does not work correctly for some NGENed types like Random.Next, System.Threading.Monitor & more
- // Mono.Cecil reports different metadata token value than ClrMD for the same method
+ // Mono.Cecil reports different metadata token value than ClrMD for the same method
// so the last chance is to try to match them by... name (I don't like it, but I have no better idea for now)
var unifiedSignature = CecilNameToClrmdName(methodReference);
- // the signature does not contain return type, so if there are few methods
+ // the signature does not contain return type, so if there are few methods
// with same name and arguments but different return type, we can do nothing
var methodsMatchingSignature = declaringType.Methods.Where(method => method.GetFullSignature() == unifiedSignature).ToArray();
@@ -425,8 +478,8 @@ namespace BenchmarkDotNet.Disassembler
// Cecil returns sth like "Boolean&"
// ClrMD expects sth like "Boolean ByRef
- static string CecilNameToClrmdName(ParameterDefinition param)
- => param.ParameterType.IsByReference
+ static string CecilNameToClrmdName(ParameterDefinition param)
+ => param.ParameterType.IsByReference
? param.ParameterType.Name.Replace("&", string.Empty) + " ByRef"
: param.ParameterType.Name;
@@ -480,7 +533,7 @@ namespace BenchmarkDotNet.Disassembler
class Settings
{
- private Settings(int processId, string typeName, string methodName, bool printAsm, bool printIL, bool printSource, bool printPrologAndEpilog, int recursiveDepth, string resultsPath)
+ private Settings(int processId, string typeName, string methodName, bool printAsm, bool printIL, bool printSource, bool printPrologAndEpilog, int recursiveDepth, string resultsPath, string[] excludedNamespaces)
{
ProcessId = processId;
TypeName = typeName;
@@ -491,6 +544,7 @@ namespace BenchmarkDotNet.Disassembler
PrintPrologAndEpilog = printPrologAndEpilog;
RecursiveDepth = methodName == DisassemblerConstants.DisassemblerEntryMethodName && recursiveDepth != int.MaxValue ? recursiveDepth + 1 : recursiveDepth;
ResultsPath = resultsPath;
+ ExcludedNamespaces = excludedNamespaces;
}
internal int ProcessId { get; }
@@ -502,6 +556,7 @@ namespace BenchmarkDotNet.Disassembler
internal bool PrintPrologAndEpilog { get; }
internal int RecursiveDepth { get; }
internal string ResultsPath { get; }
+ internal string[] ExcludedNamespaces { get; }
internal static Settings FromArgs(string[] args)
=> new Settings(
@@ -513,7 +568,8 @@ namespace BenchmarkDotNet.Disassembler
printSource: bool.Parse(args[5]),
printPrologAndEpilog: bool.Parse(args[6]),
recursiveDepth: int.Parse(args[7]),
- resultsPath: args[8]
+ resultsPath: args[8],
+ excludedNamespaces: args[9].Split(';')
);
}
@@ -537,11 +593,13 @@ namespace BenchmarkDotNet.Disassembler
{
internal ClrMethod Method { get; }
internal int Depth { get; }
+ internal string Namespace { get; }
- internal MethodInfo(ClrMethod method, int depth)
+ internal MethodInfo(ClrMethod method, int depth, string @namespace)
{
Method = method;
Depth = depth;
+ Namespace = @namespace;
}
}
diff --git a/src/BenchmarkDotNet/Diagnosers/DisassemblyDiagnoserConfig.cs b/src/BenchmarkDotNet/Diagnosers/DisassemblyDiagnoserConfig.cs
index d290fd2f..8844e107 100644
--- a/src/BenchmarkDotNet/Diagnosers/DisassemblyDiagnoserConfig.cs
+++ b/src/BenchmarkDotNet/Diagnosers/DisassemblyDiagnoserConfig.cs
@@ -15,14 +15,16 @@ namespace BenchmarkDotNet.Diagnosers
/// <param name="printSource">C# source code will be printed. False by default.</param>
/// <param name="printPrologAndEpilog">ASM for prolog and epilog will be printed. False by default.</param>
/// <param name="recursiveDepth">Includes called methods to given level. 1 by default, indexed from 1. To print just benchmark set to 0</param>
+ /// <param name="excludedNamespaces">Excludes called methods from the given namespace list (separated by a semicolon). Methods outside of these namespaces but called within said namespaces are still printed.</param>
public DisassemblyDiagnoserConfig(bool printAsm = true, bool printIL = false, bool printSource = false, bool printPrologAndEpilog = false,
- int recursiveDepth = 1)
+ int recursiveDepth = 1, string[] excludedNamespaces = null)
{
PrintAsm = printAsm;
PrintIL = printIL;
PrintSource = printSource;
PrintPrologAndEpilog = printPrologAndEpilog;
RecursiveDepth = recursiveDepth;
+ ExcludedNamespaces = excludedNamespaces ?? new string[0];
}
public bool PrintAsm { get; }
@@ -30,5 +32,6 @@ namespace BenchmarkDotNet.Diagnosers
public bool PrintSource { get; }
public bool PrintPrologAndEpilog { get; }
public int RecursiveDepth { get; }
+ public string[] ExcludedNamespaces { get; }
}
}
\ No newline at end of file
diff --git a/src/BenchmarkDotNet/Diagnosers/MonoDisassembler.cs b/src/BenchmarkDotNet/Diagnosers/MonoDisassembler.cs
index bf0aaab5..6bfc0cca 100644
--- a/src/BenchmarkDotNet/Diagnosers/MonoDisassembler.cs
+++ b/src/BenchmarkDotNet/Diagnosers/MonoDisassembler.cs
@@ -19,6 +19,7 @@ namespace BenchmarkDotNet.Diagnosers
{
private readonly bool printAsm, printIL, printSource, printPrologAndEpilog;
private readonly int recursiveDepth;
+ private readonly string[] excludedNamespaces;
internal MonoDisassembler(DisassemblyDiagnoserConfig config)
{
@@ -27,6 +28,7 @@ namespace BenchmarkDotNet.Diagnosers
printSource = config.PrintSource;
printPrologAndEpilog = config.PrintPrologAndEpilog;
recursiveDepth = config.RecursiveDepth;
+ excludedNamespaces = config.ExcludedNamespaces;
}
internal static DisassemblyResult Disassemble(BenchmarkCase benchmarkCase, MonoRuntime mono)
@@ -37,25 +39,25 @@ namespace BenchmarkDotNet.Diagnosers
string fqnMethod = GetMethodName(benchmarkTarget);
string llvmFlag = GetLlvmFlag(benchmarkCase.Job);
string exePath = benchmarkTarget.Type.GetTypeInfo().Assembly.Location;
-
+
var environmentVariables = new Dictionary<string, string> { ["MONO_VERBOSE_METHOD"] = fqnMethod };
string monoPath = mono?.CustomPath ?? "mono";
string arguments = $"--compile {fqnMethod} {llvmFlag} {exePath}";
-
+
var output = ProcessHelper.RunAndReadOutputLineByLine(monoPath, arguments, environmentVariables, includeErrors: true);
string commandLine = $"{GetEnvironmentVariables(environmentVariables)} {monoPath} {arguments}";
-
+
return OutputParser.Parse(output, benchmarkTarget.WorkloadMethod.Name, commandLine);
}
- private static string GetEnvironmentVariables(Dictionary<string, string> environmentVariables)
+ private static string GetEnvironmentVariables(Dictionary<string, string> environmentVariables)
=> string.Join(" ", environmentVariables.Select(e => $"{e.Key}={e.Value}"));
private static string GetMethodName(Descriptor descriptor)
=> $"{descriptor.Type.GetTypeInfo().Namespace}.{descriptor.Type.GetTypeInfo().Name}:{descriptor.WorkloadMethod.Name}";
// TODO: use resolver
- // TODO: introduce a global helper method for LlvmFlag
+ // TODO: introduce a global helper method for LlvmFlag
private static string GetLlvmFlag(Job job) =>
job.ResolveValue(EnvironmentMode.JitCharacteristic, Jit.Default) == Jit.Llvm ? "--llvm" : "--nollvm";
@@ -112,7 +114,7 @@ namespace BenchmarkDotNet.Diagnosers
};
}
- private static DisassemblyResult CreateErrorResult([ItemCanBeNull] IReadOnlyList<string> input,
+ private static DisassemblyResult CreateErrorResult([ItemCanBeNull] IReadOnlyList<string> input,
string methodName, string commandLine, string message)
{
return new DisassemblyResult
diff --git a/src/BenchmarkDotNet/Diagnosers/WindowsDisassembler.cs b/src/BenchmarkDotNet/Diagnosers/WindowsDisassembler.cs
index 37d6e4ae..67311040 100644
--- a/src/BenchmarkDotNet/Diagnosers/WindowsDisassembler.cs
+++ b/src/BenchmarkDotNet/Diagnosers/WindowsDisassembler.cs
@@ -20,6 +20,7 @@ namespace BenchmarkDotNet.Diagnosers
{
private readonly bool printAsm, printIL, printSource, printPrologAndEpilog;
private readonly int recursiveDepth;
+ private readonly string[] excludedNamespaces;
internal WindowsDisassembler(DisassemblyDiagnoserConfig config)
{
@@ -28,6 +29,7 @@ namespace BenchmarkDotNet.Diagnosers
printSource = config.PrintSource;
printPrologAndEpilog = config.PrintPrologAndEpilog;
recursiveDepth = config.RecursiveDepth;
+ excludedNamespaces = config.ExcludedNamespaces;
}
internal DisassemblyResult Disassemble(DiagnoserActionParameters parameters)
@@ -141,7 +143,8 @@ namespace BenchmarkDotNet.Diagnosers
.Append(printSource).Append(' ')
.Append(printPrologAndEpilog).Append(' ')
.Append(recursiveDepth).Append(' ')
- .Append($"\"{resultsPath}\"")
+ .Append($"\"{resultsPath}\"").Append(' ')
+ .Append($"\"{string.Join(";", excludedNamespaces)}\"")
.ToString();
// code copied from https://stackoverflow.com/a/33206186/5852046
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment