Skip to content

Commit

Permalink
Mono Support for DisassemblyDiagnoser #541
Browse files Browse the repository at this point in the history
  • Loading branch information
morgan-kn committed Jan 30, 2018
1 parent 06e66ab commit d8ac43e
Show file tree
Hide file tree
Showing 6 changed files with 86 additions and 168 deletions.
2 changes: 2 additions & 0 deletions src/BenchmarkDotNet.Core/Diagnosers/CopiedDataContracts.cs
Expand Up @@ -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
Expand Down
77 changes: 27 additions & 50 deletions src/BenchmarkDotNet.Core/Diagnosers/MonoDisassembler.cs
Expand Up @@ -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<string, string> { ["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<string, string> 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<string> input, string monoMethodName, string methodName)
internal static DisassemblyResult Parse(IReadOnlyList<string> input, string methodName, string commandLine)
{
var instructions = new List<Code>();

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
{
Expand All @@ -74,39 +66,24 @@ internal static DisassemblyResult Parse(IReadOnlyList<string> 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");
}
}
}
3 changes: 3 additions & 0 deletions src/BenchmarkDotNet.Core/Exporters/RawDisassemblyExporter.cs
Expand Up @@ -84,6 +84,9 @@ private void Export(ILogger logger, DisassemblyResult disassemblyResult, Benchma

evenMap = !evenMap;
}

if(!string.IsNullOrEmpty(method.CommandLine))
logger.WriteLine($"<tr><td colspan=\"2\">{method.CommandLine}</td></tr>");

logger.WriteLine("<tr><td colspan=\"{2}\">&nbsp;</td></tr>");
}
Expand Down
6 changes: 5 additions & 1 deletion src/BenchmarkDotNet.Core/Helpers/ProcessHelper.cs
Expand Up @@ -40,7 +40,7 @@ internal static string RunAndReadOutput(string fileName, string arguments = "")
}
}

internal static IReadOnlyList<string> RunAndReadOutputLineByLine(string fileName, string arguments = "")
internal static IReadOnlyList<string> RunAndReadOutputLineByLine(string fileName, string arguments = "", Dictionary<string, string> environmentVariables = null)
{
var output = new List<string>(20000);

Expand All @@ -55,6 +55,10 @@ internal static IReadOnlyList<string> 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);
Expand Down
2 changes: 1 addition & 1 deletion src/BenchmarkDotNet.Core/Running/BenchmarkRunnerCore.cs
Expand Up @@ -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);
Expand Down
164 changes: 48 additions & 116 deletions tests/BenchmarkDotNet.Tests/MonoDisassemblyOutputParserTests.cs
Expand Up @@ -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()
{
Expand All @@ -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" }
}
}
}
Expand All @@ -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);
}
}
}
}

0 comments on commit d8ac43e

Please sign in to comment.