Last active
October 15, 2018 13:21
-
-
Save Warpten/c8f0d809f346684401df89ab85139cd9 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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