From d8ac43e5da1420c6a941091c04abc39c4816aa04 Mon Sep 17 00:00:00 2001 From: morgan_kn Date: Wed, 31 Jan 2018 01:11:42 +0300 Subject: [PATCH] Mono Support for DisassemblyDiagnoser #541 --- .../Diagnosers/CopiedDataContracts.cs | 2 + .../Diagnosers/MonoDisassembler.cs | 77 +++----- .../Exporters/RawDisassemblyExporter.cs | 3 + .../Helpers/ProcessHelper.cs | 6 +- .../Running/BenchmarkRunnerCore.cs | 2 +- .../MonoDisassemblyOutputParserTests.cs | 164 +++++------------- 6 files changed, 86 insertions(+), 168 deletions(-) diff --git a/src/BenchmarkDotNet.Core/Diagnosers/CopiedDataContracts.cs b/src/BenchmarkDotNet.Core/Diagnosers/CopiedDataContracts.cs index e5413b27df..48b0b77355 100644 --- a/src/BenchmarkDotNet.Core/Diagnosers/CopiedDataContracts.cs +++ b/src/BenchmarkDotNet.Core/Diagnosers/CopiedDataContracts.cs @@ -53,6 +53,8 @@ public class DisassembledMethod public string Problem { get; set; } public Map[] Maps { get; set; } + + public string CommandLine { get; set; } } public class DisassemblyResult diff --git a/src/BenchmarkDotNet.Core/Diagnosers/MonoDisassembler.cs b/src/BenchmarkDotNet.Core/Diagnosers/MonoDisassembler.cs index f87b9e5e2c..d19f19b483 100644 --- a/src/BenchmarkDotNet.Core/Diagnosers/MonoDisassembler.cs +++ b/src/BenchmarkDotNet.Core/Diagnosers/MonoDisassembler.cs @@ -27,45 +27,37 @@ internal DisassemblyResult Disassemble(Benchmark benchmark, MonoRuntime mono) { Debug.Assert(mono == null || !RuntimeInformation.IsMono(), "Must never be called for Non-Mono benchmarks"); - var monoMethodName = GetMethodName(benchmark.Target); - - var output = ProcessHelper.RunAndReadOutputLineByLine( - mono?.CustomPath ?? "mono", - "-v -v -v -v " - + $"--compile {monoMethodName} " - + (benchmark.Job.Env.Jit == Jit.Llvm ? "--llvm" : "--nollvm") - + $" \"{benchmark.Target.Type.GetTypeInfo().Assembly.Location}\""); - - return OutputParser.Parse(output, monoMethodName, benchmark.Target.Method.Name); + var benchmarkTarget = benchmark.Target; + string fqnMethod = GetMethodName(benchmarkTarget); + string exePath = benchmarkTarget.Type.GetTypeInfo().Assembly.Location; + + var environmentVariables = new Dictionary { ["MONO_VERBOSE_METHOD"] = fqnMethod }; + string monoPath = mono?.CustomPath ?? "mono"; + string arguments = $"--compile {fqnMethod} {exePath}"; + + var output = ProcessHelper.RunAndReadOutputLineByLine(monoPath, arguments, environmentVariables); + string commandLine = $"{GetEnvironmentVariables(environmentVariables)} {monoPath} {arguments}"; + + return OutputParser.Parse(output, benchmarkTarget.Method.Name, commandLine); } - static string GetMethodName(Target target) + private static string GetEnvironmentVariables(Dictionary environmentVariables) + => string.Join(" ", environmentVariables.Select(e => $"{e.Key}={e.Value}")); + + private static string GetMethodName(Target target) => $"{target.Type.GetTypeInfo().Namespace}.{target.Type.GetTypeInfo().Name}:{target.Method.Name}"; internal static class OutputParser { - internal static DisassemblyResult Parse(IReadOnlyList input, string monoMethodName, string methodName) + internal static DisassemblyResult Parse(IReadOnlyList input, string methodName, string commandLine) { var instructions = new List(); - bool found = false; - - foreach (var line in input.Reverse()) - { - if (!found) - { - if (IsStartLine(line, monoMethodName)) - found = true; - - continue; - } - - if (IsEndLine(line)) - break; + var listing = input.SkipWhile(i => !i.Contains("(__TEXT,__text) section")).Skip(2); + foreach (string line in listing) if (TryParseInstruction(line, out var instruction)) instructions.Add(instruction); - } return new DisassemblyResult { @@ -74,39 +66,24 @@ internal static DisassemblyResult Parse(IReadOnlyList input, string mono new DisassembledMethod { Name = methodName, - Maps = new [] { new Map { Instructions = instructions.ToArray() } } + Maps = new [] { new Map { Instructions = instructions.ToArray() } }, + CommandLine = commandLine } } }; } - - // the input is sth like " 4 storei4_membase_reg [%edi + 0xc] <- %eax" + + //line example: 0000000000000000 subq $0x28, %rsp private static bool TryParseInstruction(string line, out Code instruction) { instruction = null; - - // in the future we could parse it, use Mono.Cecil and combine the il seq point with IL for given method as we do for regular .NET - if (line.Contains("il_seq_point")) + string trimmed = line?.Trim(); + if (string.IsNullOrEmpty(trimmed)) return false; - - var trimmed = line.Trim(); - if (string.IsNullOrEmpty(trimmed) || !char.IsDigit(trimmed[0])) - return false; - - int startIndex = 0; - while (char.IsDigit(trimmed[startIndex]) || char.IsWhiteSpace(trimmed[startIndex])) - startIndex++; - - instruction = new Code { TextRepresentation = trimmed.Substring(startIndex) }; - + var splitted = trimmed.Split(new [] { '\t' }, 2); + instruction = new Code { TextRepresentation = splitted.Last() }; return true; } - - private static bool IsEndLine(string line) - => string.IsNullOrWhiteSpace(line) || line.Contains("liveness"); - - private static bool IsStartLine(string line, string methodName) - => line != null && line.Contains(methodName) && line.Contains("emitted at"); } } } \ No newline at end of file diff --git a/src/BenchmarkDotNet.Core/Exporters/RawDisassemblyExporter.cs b/src/BenchmarkDotNet.Core/Exporters/RawDisassemblyExporter.cs index 036cac901d..37d62bc72e 100644 --- a/src/BenchmarkDotNet.Core/Exporters/RawDisassemblyExporter.cs +++ b/src/BenchmarkDotNet.Core/Exporters/RawDisassemblyExporter.cs @@ -84,6 +84,9 @@ private void Export(ILogger logger, DisassemblyResult disassemblyResult, Benchma evenMap = !evenMap; } + + if(!string.IsNullOrEmpty(method.CommandLine)) + logger.WriteLine($"{method.CommandLine}"); logger.WriteLine(" "); } diff --git a/src/BenchmarkDotNet.Core/Helpers/ProcessHelper.cs b/src/BenchmarkDotNet.Core/Helpers/ProcessHelper.cs index 7b63163665..1bfd74fff0 100644 --- a/src/BenchmarkDotNet.Core/Helpers/ProcessHelper.cs +++ b/src/BenchmarkDotNet.Core/Helpers/ProcessHelper.cs @@ -40,7 +40,7 @@ internal static string RunAndReadOutput(string fileName, string arguments = "") } } - internal static IReadOnlyList RunAndReadOutputLineByLine(string fileName, string arguments = "") + internal static IReadOnlyList RunAndReadOutputLineByLine(string fileName, string arguments = "", Dictionary environmentVariables = null) { var output = new List(20000); @@ -55,6 +55,10 @@ internal static IReadOnlyList RunAndReadOutputLineByLine(string fileName RedirectStandardError = true }; + if(environmentVariables != null) + foreach (var environmentVariable in environmentVariables) + processStartInfo.Environment[environmentVariable.Key] = environmentVariable.Value; + using (var process = new Process { StartInfo = processStartInfo }) { process.OutputDataReceived += (sender, args) => output.Add(args.Data); diff --git a/src/BenchmarkDotNet.Core/Running/BenchmarkRunnerCore.cs b/src/BenchmarkDotNet.Core/Running/BenchmarkRunnerCore.cs index c5de14106e..cd79725d00 100644 --- a/src/BenchmarkDotNet.Core/Running/BenchmarkRunnerCore.cs +++ b/src/BenchmarkDotNet.Core/Running/BenchmarkRunnerCore.cs @@ -366,7 +366,7 @@ private static BenchmarkReport RunCore(Benchmark benchmark, ILogger logger, Read { logger.WriteLineInfo("// Run, Diagnostic [SeparateLogic]"); - separateLogicCompositeDiagnoser.Handle(HostSignal.AfterAll, new DiagnoserActionParameters(null, benchmark, config)); + separateLogicCompositeDiagnoser.Handle(HostSignal.SeparateLogic, new DiagnoserActionParameters(null, benchmark, config)); } return (executeResults, gcStats); diff --git a/tests/BenchmarkDotNet.Tests/MonoDisassemblyOutputParserTests.cs b/tests/BenchmarkDotNet.Tests/MonoDisassemblyOutputParserTests.cs index d669c1a2d6..4ab70f8581 100644 --- a/tests/BenchmarkDotNet.Tests/MonoDisassemblyOutputParserTests.cs +++ b/tests/BenchmarkDotNet.Tests/MonoDisassemblyOutputParserTests.cs @@ -11,103 +11,33 @@ public class MonoDisassemblyOutputParserTests public void CanParseMonoDisassemblyOutput() { const string input = @" -LOCAL REGALLOC BLOCK 2: - 1 il_seq_point il: 0x0 - 2 loadi4_membase R11 <- [%edi + 0xc] - 3 int_add_imm R13 <- R11 [1] clobbers: 1 - 4 storei4_membase_reg [%edi + 0xc] <- R13 - 5 il_seq_point il: 0xe - 6 move R16 <- R13 - 7 int_add_imm R18 <- R16 [1] clobbers: 1 - 8 storei4_membase_reg [%edi + 0xc] <- R18 - 9 il_seq_point il: 0x1c - 10 move R21 <- R18 - 11 int_add_imm R23 <- R21 [1] clobbers: 1 - 12 storei4_membase_reg [%edi + 0xc] <- R23 - 13 il_seq_point il: 0x2a - 14 move R26 <- R23 - 15 int_add_imm R28 <- R26 [1] clobbers: 1 - 16 storei4_membase_reg [%edi + 0xc] <- R28 - 17 il_seq_point il: 0x38 -liveness: %edi [4 - 0] -liveness: R11 [2 - 2] -liveness: R13 [3 - 3] -liveness: R16 [6 - 6] -liveness: R18 [7 - 7] -liveness: R21 [10 - 10] -liveness: R23 [11 - 11] -liveness: R26 [14 - 14] -liveness: R28 [15 - 15] -processing: 17 il_seq_point il: 0x38 - 17 il_seq_point il: 0x38 -processing: 16 storei4_membase_reg [%edi + 0xc] <- R28 - assigned sreg1 %eax to R28 - 16 storei4_membase_reg [%edi + 0xc] <- %eax -processing: 15 int_add_imm R28 <- R26 [1] clobbers: 1 - assigned dreg %eax to dest R28 - freeable %eax (R28) (born in 15) - assigned sreg1 %eax to R26 - 15 int_add_imm %eax <- %eax [1] clobbers: 1 -processing: 14 move R26 <- R23 - assigned dreg %eax to dest R26 - freeable %eax (R26) (born in 14) - assigned sreg1 %eax to R23 - 14 move %eax <- %eax -processing: 13 il_seq_point il: 0x2a - 13 il_seq_point il: 0x2a -processing: 12 storei4_membase_reg [%edi + 0xc] <- R23 - 12 storei4_membase_reg [%edi + 0xc] <- %eax -processing: 11 int_add_imm R23 <- R21 [1] clobbers: 1 - assigned dreg %eax to dest R23 - freeable %eax (R23) (born in 11) - assigned sreg1 %eax to R21 - 11 int_add_imm %eax <- %eax [1] clobbers: 1 -processing: 10 move R21 <- R18 - assigned dreg %eax to dest R21 - freeable %eax (R21) (born in 10) - assigned sreg1 %eax to R18 - 10 move %eax <- %eax -processing: 9 il_seq_point il: 0x1c - 9 il_seq_point il: 0x1c -processing: 8 storei4_membase_reg [%edi + 0xc] <- R18 - 8 storei4_membase_reg [%edi + 0xc] <- %eax -processing: 7 int_add_imm R18 <- R16 [1] clobbers: 1 - assigned dreg %eax to dest R18 - freeable %eax (R18) (born in 7) - assigned sreg1 %eax to R16 - 7 int_add_imm %eax <- %eax [1] clobbers: 1 -processing: 6 move R16 <- R13 - assigned dreg %eax to dest R16 - freeable %eax (R16) (born in 6) - assigned sreg1 %eax to R13 - 6 move %eax <- %eax -processing: 5 il_seq_point il: 0xe - 5 il_seq_point il: 0xe -processing: 4 storei4_membase_reg [%edi + 0xc] <- R13 - 4 storei4_membase_reg [%edi + 0xc] <- %eax -processing: 3 int_add_imm R13 <- R11 [1] clobbers: 1 - assigned dreg %eax to dest R13 - freeable %eax (R13) (born in 3) - assigned sreg1 %eax to R11 - 3 int_add_imm %eax <- %eax [1] clobbers: 1 -processing: 2 loadi4_membase R11 <- [%edi + 0xc] - assigned dreg %eax to dest R11 - freeable %eax (R11) (born in 2) - 2 loadi4_membase %eax <- [%edi + 0xc] -processing: 1 il_seq_point il: 0x0 - 1 il_seq_point il: 0x0 -CFA: [0] def_cfa: %esp+0x4 -CFA: [0] offset: unknown at cfa-0x4 -CFA: [1] def_cfa_offset: 0x8 -CFA: [1] offset: %ebp at cfa-0x8 -CFA: [3] def_cfa_reg: %ebp -CFA: [4] offset: %edi at cfa-0xc -Argument 0 assigned to register %edi -Basic block 0 starting at offset 0xa -Basic block 2 starting at offset 0xa -Basic block 1 starting at offset 0x1d -Method void BenchmarkDotNet.Samples.CPU.Cpu_Atomics:NoLock () emitted at 03AC11D0 to 03AC11F6 (code length 38) [BenchmarkDotNet.Samples.exe] -"; +CFA: [0] def_cfa: %rsp+0x8 +CFA: [0] offset: unknown at cfa-0x8 +CFA: [4] def_cfa_offset: 0x10 +CFA: [8] offset: %r15 at cfa-0x10 +Basic block 0 starting at offset 0xb +Basic block 2 starting at offset 0xb +Basic block 1 starting at offset 0x27 +CFA: [2f] def_cfa: %rsp+0x8 +Method void BenchmarkDotNet.Samples.CPU.Cpu_Atomics:NoLock () emitted at 0x1027cdf80 to 0x1027cdfb0 (code length 48) [BenchmarkDotNet.Samples.exe] +/var/folders/ld/p9yn04fs3ys6h_dkyxvv95_40000gn/T/.WuSVhL: +(__TEXT,__text) section +chmarkDotNet_Samples_CPU_Cpu_Atomics_NoLock: +0000000000000000 subq $0x8, %rsp +0000000000000004 movq %r15, (%rsp) +0000000000000008 movq %rdi, %r15 +000000000000000b movslq 0x18(%r15), %rax +000000000000000f incl %eax +0000000000000011 movl %eax, 0x18(%r15) +0000000000000015 incl %eax +0000000000000017 movl %eax, 0x18(%r15) +000000000000001b incl %eax +000000000000001d movl %eax, 0x18(%r15) +0000000000000021 incl %eax +0000000000000023 movl %eax, 0x18(%r15) +0000000000000027 movq (%rsp), %r15 +000000000000002b addq $0x8, %rsp +000000000000002f retq"; var expected = new DisassemblyResult() { @@ -116,28 +46,33 @@ public void CanParseMonoDisassemblyOutput() new DisassembledMethod() { Name = "NoLock", - Maps = new Map[] + Maps = new[] { new Map() { Instructions = new[] { - new Diagnosers.Code { TextRepresentation = "loadi4_membase %eax <- [%edi + 0xc]" }, + new Diagnosers.Code { TextRepresentation = "subq\t$0x8, %rsp" }, - new Diagnosers.Code { TextRepresentation = "int_add_imm %eax <- %eax [1] clobbers: 1" }, - new Diagnosers.Code { TextRepresentation = "storei4_membase_reg [%edi + 0xc] <- %eax" }, - new Diagnosers.Code { TextRepresentation = "move %eax <- %eax" }, + new Diagnosers.Code { TextRepresentation = "movq\t%r15, (%rsp)" }, + new Diagnosers.Code { TextRepresentation = "movq\t%rdi, %r15" }, + new Diagnosers.Code { TextRepresentation = "movslq\t0x18(%r15), %rax" }, - new Diagnosers.Code { TextRepresentation = "int_add_imm %eax <- %eax [1] clobbers: 1" }, - new Diagnosers.Code { TextRepresentation = "storei4_membase_reg [%edi + 0xc] <- %eax" }, - new Diagnosers.Code { TextRepresentation = "move %eax <- %eax" }, - - new Diagnosers.Code { TextRepresentation = "int_add_imm %eax <- %eax [1] clobbers: 1" }, - new Diagnosers.Code { TextRepresentation = "storei4_membase_reg [%edi + 0xc] <- %eax" }, - new Diagnosers.Code { TextRepresentation = "move %eax <- %eax" }, - - new Diagnosers.Code { TextRepresentation = "int_add_imm %eax <- %eax [1] clobbers: 1" }, - new Diagnosers.Code { TextRepresentation = "storei4_membase_reg [%edi + 0xc] <- %eax" }, + new Diagnosers.Code { TextRepresentation = "incl\t%eax" }, + new Diagnosers.Code { TextRepresentation = "movl\t%eax, 0x18(%r15)" }, + + new Diagnosers.Code { TextRepresentation = "incl\t%eax" }, + new Diagnosers.Code { TextRepresentation = "movl\t%eax, 0x18(%r15)" }, + + new Diagnosers.Code { TextRepresentation = "incl\t%eax" }, + new Diagnosers.Code { TextRepresentation = "movl\t%eax, 0x18(%r15)" }, + + new Diagnosers.Code { TextRepresentation = "incl\t%eax" }, + new Diagnosers.Code { TextRepresentation = "movl\t%eax, 0x18(%r15)" }, + + new Diagnosers.Code { TextRepresentation = "movq\t(%rsp), %r15" }, + new Diagnosers.Code { TextRepresentation = "addq\t$0x8, %rsp" }, + new Diagnosers.Code { TextRepresentation = "retq" } } } } @@ -147,17 +82,14 @@ public void CanParseMonoDisassemblyOutput() var disassemblyResult = MonoDisassembler.OutputParser.Parse( input.Split(new[] { "\r\n", "\n" }, StringSplitOptions.None), - "BenchmarkDotNet.Samples.CPU.Cpu_Atomics:NoLock", - "NoLock"); + "NoLock", commandLine: string.Empty); Assert.Equal(expected.Methods.Single().Name, disassemblyResult.Methods.Single().Name); Assert.Equal(expected.Methods[0].Maps[0].Instructions.Length, disassemblyResult.Methods[0].Maps[0].Instructions.Length); for (int i = 0; i < expected.Methods[0].Maps[0].Instructions.Length; i++) - { Assert.Equal(expected.Methods[0].Maps[0].Instructions[i].TextRepresentation, disassemblyResult.Methods[0].Maps[0].Instructions[i].TextRepresentation); - } } } } \ No newline at end of file