diff --git a/src/BenchmarkDotNet/BenchmarkDotNet.csproj b/src/BenchmarkDotNet/BenchmarkDotNet.csproj
index fbf324e611..5a65868663 100644
--- a/src/BenchmarkDotNet/BenchmarkDotNet.csproj
+++ b/src/BenchmarkDotNet/BenchmarkDotNet.csproj
@@ -27,6 +27,7 @@
+
diff --git a/src/BenchmarkDotNet/Disassemblers/ClrMdArgs.cs b/src/BenchmarkDotNet/Disassemblers/ClrMdArgs.cs
index cf26d90e6b..8abbf910d7 100644
--- a/src/BenchmarkDotNet/Disassemblers/ClrMdArgs.cs
+++ b/src/BenchmarkDotNet/Disassemblers/ClrMdArgs.cs
@@ -1,6 +1,6 @@
-using System;
+using BenchmarkDotNet.Serialization;
using System.Linq;
-using SimpleJson;
+using System.Text.Json.Serialization;
#nullable enable
@@ -8,14 +8,31 @@ namespace BenchmarkDotNet.Disassemblers
{
internal struct ClrMdArgs(int processId, string typeName, string methodName, bool printSource, int maxDepth, string syntax, string tfm, string[] filters, string resultsPath = "")
{
+ [JsonIgnore]
internal int ProcessId = processId;
- internal string TypeName = typeName;
+
+ [JsonIgnore]
+ internal string TypeName = typeName ?? "";
+
+ [JsonInclude]
internal string MethodName = methodName;
+
+ [JsonInclude]
internal bool PrintSource = printSource;
+
+ [JsonInclude]
internal int MaxDepth = methodName == DisassemblerConstants.DisassemblerEntryMethodName && maxDepth != int.MaxValue ? maxDepth + 1 : maxDepth;
+
+ [JsonInclude]
internal string[] Filters = filters;
+
+ [JsonInclude]
internal string Syntax = syntax;
+
+ [JsonInclude]
internal string TargetFrameworkMoniker = tfm;
+
+ [JsonInclude]
internal string ResultsPath = resultsPath;
internal static ClrMdArgs FromArgs(string[] args)
@@ -30,46 +47,5 @@ internal static ClrMdArgs FromArgs(string[] args)
tfm: args[7],
filters: [.. args.Skip(8)]
);
-
- internal readonly string Serialize()
- {
- SimpleJsonSerializer.CurrentJsonSerializerStrategy.Indent = false;
- var jsonObject = new JsonObject()
- {
- [nameof(MethodName)] = MethodName,
- [nameof(PrintSource)] = PrintSource,
- [nameof(MaxDepth)] = MaxDepth,
- [nameof(Syntax)] = Syntax,
- [nameof(TargetFrameworkMoniker)] = TargetFrameworkMoniker,
- [nameof(ResultsPath)] = ResultsPath,
- };
- var filters = new JsonArray(Filters.Length);
- foreach (var filter in Filters)
- {
- filters.Add(filter);
- }
- jsonObject[nameof(Filters)] = filters;
- return jsonObject.ToString();
- }
-
- internal void Deserialize(string? json)
- {
- var jsonObject = SimpleJsonSerializer.DeserializeObject(json);
- if (jsonObject == null)
- return;
-
- MethodName = (string)jsonObject[nameof(MethodName)];
- PrintSource = (bool)jsonObject[nameof(PrintSource)];
- MaxDepth = Convert.ToInt32(jsonObject[nameof(MaxDepth)]);
- Syntax = (string) jsonObject[nameof(Syntax)];
- TargetFrameworkMoniker = (string) jsonObject[nameof(TargetFrameworkMoniker)];
- ResultsPath = (string) jsonObject[nameof(ResultsPath)];
- var filters = (JsonArray) jsonObject[nameof(Filters)];
- Filters = new string[filters.Count];
- for (int i = 0; i < filters.Count; ++i)
- {
- Filters[i] = (string) filters[i];
- }
- }
}
}
\ No newline at end of file
diff --git a/src/BenchmarkDotNet/Disassemblers/ClrMdDisassembler.cs b/src/BenchmarkDotNet/Disassemblers/ClrMdDisassembler.cs
index 21d780d6b7..a1b51f9e2b 100644
--- a/src/BenchmarkDotNet/Disassemblers/ClrMdDisassembler.cs
+++ b/src/BenchmarkDotNet/Disassemblers/ClrMdDisassembler.cs
@@ -26,7 +26,7 @@ private static ulong GetMinValidAddress()
if (OsDetector.IsWindows())
return ushort.MaxValue + 1;
if (OsDetector.IsLinux())
- return (ulong) Environment.SystemPageSize;
+ return (ulong)Environment.SystemPageSize;
if (OsDetector.IsMacOS())
return RuntimeInformation.GetCurrentPlatform() switch
{
@@ -121,8 +121,8 @@ internal DisassemblyResult AttachAndDisassemble(ClrMdArgs args)
return new DisassemblyResult
{
Methods = filteredMethods,
- SerializedAddressToNameMapping = state.AddressToNameMapping.Select(x => new DisassemblyResult.MutablePair { Key = x.Key, Value = x.Value }).ToArray(),
- PointerSize = (uint) IntPtr.Size
+ AddressToNameMapping = state.AddressToNameMapping,
+ PointerSize = (uint)IntPtr.Size
};
}
@@ -296,7 +296,7 @@ protected void TryTranslateAddressToName(ulong address, bool isAddressPrecodeMD,
}
var method = runtime.GetMethodByInstructionPointer(address);
- if (method is null && (address & ((uint) runtime.DataTarget.DataReader.PointerSize - 1)) == 0
+ if (method is null && (address & ((uint)runtime.DataTarget.DataReader.PointerSize - 1)) == 0
&& runtime.DataTarget.DataReader.ReadPointer(address, out ulong newAddress) && IsValidAddress(newAddress))
{
method = runtime.GetMethodByInstructionPointer(newAddress);
diff --git a/src/BenchmarkDotNet/Disassemblers/DataContracts.cs b/src/BenchmarkDotNet/Disassemblers/DataContracts.cs
index b2c96eb7ff..22b081650e 100644
--- a/src/BenchmarkDotNet/Disassemblers/DataContracts.cs
+++ b/src/BenchmarkDotNet/Disassemblers/DataContracts.cs
@@ -2,11 +2,14 @@
using Gee.External.Capstone.Arm64;
using Iced.Intel;
using Microsoft.Diagnostics.Runtime;
-using SimpleJson;
+
using System;
using System.Collections.Generic;
using System.Linq;
-using System.Xml.Serialization;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+using System.ComponentModel;
+
#if NET6_0_OR_GREATER
using System.Diagnostics.CodeAnalysis;
@@ -16,29 +19,17 @@
namespace BenchmarkDotNet.Disassemblers;
+[JsonPolymorphic(TypeDiscriminatorPropertyName = "$type")]
+[JsonDerivedType(typeof(Sharp), typeDiscriminator: nameof(Sharp))]
+[JsonDerivedType(typeof(IntelAsm), typeDiscriminator: nameof(IntelAsm))]
+[JsonDerivedType(typeof(Arm64Asm), typeDiscriminator: nameof(Arm64Asm))]
+[JsonDerivedType(typeof(MonoCode), typeDiscriminator: nameof(MonoCode))]
public abstract class SourceCode
{
// Closed hierarchy.
internal SourceCode() { }
public ulong InstructionPointer { get; set; }
-
- internal JsonObject Serialize()
- {
- var json = new JsonObject { ["$type"] = GetType().Name };
- Serialize(json);
- return json;
- }
-
- private protected virtual void Serialize(JsonObject json)
- {
- json[nameof(InstructionPointer)] = InstructionPointer.ToString();
- }
-
- internal virtual void Deserialize(JsonObject json)
- {
- InstructionPointer = ulong.Parse((string) json[nameof(InstructionPointer)]);
- }
}
public sealed class Sharp : SourceCode
@@ -46,24 +37,6 @@ public sealed class Sharp : SourceCode
public string Text { get; set; } = default!;
public string FilePath { get; set; } = default!;
public int LineNumber { get; set; }
-
- private protected override void Serialize(JsonObject json)
- {
- base.Serialize(json);
-
- json[nameof(Text)] = Text;
- json[nameof(FilePath)] = FilePath;
- json[nameof(LineNumber)] = LineNumber;
- }
-
- internal override void Deserialize(JsonObject json)
- {
- base.Deserialize(json);
-
- Text = (string) json[nameof(Text)];
- FilePath = (string) json[nameof(FilePath)];
- LineNumber = Convert.ToInt32(json[nameof(LineNumber)]);
- }
}
public abstract class Asm : SourceCode
@@ -74,29 +47,6 @@ internal Asm() { }
public int InstructionLength { get; set; }
public ulong? ReferencedAddress { get; set; }
public bool IsReferencedAddressIndirect { get; set; }
-
- private protected override void Serialize(JsonObject json)
- {
- base.Serialize(json);
- json[nameof(InstructionLength)] = InstructionLength;
- if (ReferencedAddress.HasValue)
- {
- json[nameof(ReferencedAddress)] = ReferencedAddress.ToString();
- }
- json[nameof(IsReferencedAddressIndirect)] = IsReferencedAddressIndirect;
- }
-
- internal override void Deserialize(JsonObject json)
- {
- base.Deserialize(json);
-
- InstructionLength = Convert.ToInt32(json[nameof(InstructionLength)]);
- if (json.TryGetValue(nameof(ReferencedAddress), out var ra))
- {
- ReferencedAddress = ulong.Parse((string) ra);
- }
- IsReferencedAddressIndirect = (bool) json[nameof(IsReferencedAddressIndirect)];
- }
}
#if NET6_0_OR_GREATER
@@ -109,158 +59,131 @@ public sealed class IntelAsm() : Asm
public Instruction Instruction { get; set; }
public override string ToString() => Instruction.ToString();
+}
- private protected override void Serialize(JsonObject json)
- {
- base.Serialize(json);
+public sealed class Arm64Asm : Asm
+{
+ [JsonInclude]
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ internal Arm64AsmData Data { get; set; }
- var instructionJson = new JsonObject();
- foreach (var property in typeof(Instruction).GetProperties())
- {
- if (property.GetSetMethod() is not null && property.GetGetMethod() is not null)
- {
- instructionJson[property.Name] = property.GetValue(Instruction) switch
- {
- ulong l => l.ToString(),
- long l => l.ToString(),
- Enum e => e.ToString(),
- var propertyValue => propertyValue
- };
- }
- }
- json[nameof(Instruction)] = instructionJson;
+ [JsonIgnore]
+ public Arm64Instruction? Instruction
+ {
+ get => Data.Instruction;
+ set => Data = Data with { Instruction = value };
}
- internal override void Deserialize(JsonObject json)
+ [JsonIgnore]
+ internal DisassembleSyntax DisassembleSyntax
{
- base.Deserialize(json);
-
- object instruction = new Instruction();
- foreach (var kvp in (JsonObject) json[nameof(Instruction)])
- {
- object value = kvp.Value;
- var property = typeof(Instruction).GetProperty(kvp.Key)!;
- var propertyType = property.PropertyType;
- if (propertyType == typeof(ulong))
- {
- value = ulong.Parse((string) value);
- }
- else if (propertyType == typeof(long))
- {
- value = long.Parse((string) value);
- }
- else if (typeof(Enum).IsAssignableFrom(propertyType))
- {
- value = Enum.Parse(propertyType, (string) value);
- }
- else if (propertyType.IsPrimitive)
- {
- value = Convert.ChangeType(value, propertyType);
- }
- property.SetValue(instruction, value);
- }
- Instruction = (Instruction)instruction;
+ get => Data.DisassembleSyntax;
+ set => Data = Data with { DisassembleSyntax = value };
}
-}
-
-public sealed class Arm64Asm : Asm
-{
- private const string AddressKey = "Arm64Address";
- private const string BytesKey = "Arm64Bytes";
- private const string SyntaxKey = "Arm64Syntax";
-
- public Arm64Instruction? Instruction { get; set; }
- internal DisassembleSyntax DisassembleSyntax { get; set; }
public override string ToString() => Instruction?.ToString() ?? "";
- private protected override void Serialize(JsonObject json)
+ // Wrapper class to hold Arm64 instruction and disassemble syntax.
+ [JsonConverter(typeof(Arm64AsmDataConverter))]
+ internal record struct Arm64AsmData
{
- base.Serialize(json);
+ internal DisassembleSyntax DisassembleSyntax { get; set; }
- // We only need the address, bytes, and syntax to reconstruct the instruction.
- if (Instruction?.Bytes?.Length > 0)
- {
- json[AddressKey] = Instruction.Address.ToString();
- json[BytesKey] = Convert.ToBase64String(Instruction.Bytes);
- json[SyntaxKey] = (int)DisassembleSyntax;
- }
+ public Arm64Instruction? Instruction { get; set; }
}
- internal override void Deserialize(JsonObject json)
+ // Custom JsonConverter for Arm64AsmData.
+ internal class Arm64AsmDataConverter : JsonConverter
{
- base.Deserialize(json);
+ private const string Arm64AddressKey = "arm64Address";
+ private const string Arm64BytesKey = "arm64Bytes";
+ private const string Arm64SyntaxKey = "arm64Syntax";
- if (json.TryGetValue(BytesKey, out var bytes64))
+ public override Arm64AsmData Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
- // Use the Capstone disassembler to recreate the instruction from the bytes.
+ if (reader.TokenType != JsonTokenType.StartObject)
+ throw new JsonException();
+
+ long instructionAddress = default;
+ byte[] instructionBytes = [];
+ DisassembleSyntax syntax = DisassembleSyntax.Masm;
+
+ while (reader.Read())
+ {
+ if (reader.TokenType == JsonTokenType.EndObject)
+ break;
+
+ if (reader.TokenType != JsonTokenType.PropertyName)
+ throw new JsonException();
+
+ string propertyName = reader.GetString()!;
+ reader.Read();
+
+ switch (propertyName)
+ {
+ case Arm64AddressKey:
+ instructionAddress = reader.GetInt64();
+ break;
+
+ case Arm64BytesKey:
+ instructionBytes = reader.GetBytesFromBase64();
+ break;
+
+ case Arm64SyntaxKey:
+ var syntaxValue = reader.GetString()!;
+ syntax = syntaxValue switch
+ {
+ "Intel" => DisassembleSyntax.Intel,
+ "Att" => DisassembleSyntax.Att,
+ "Masm" => DisassembleSyntax.Masm,
+ _ => DisassembleSyntax.Masm,
+ };
+ break;
+
+ default:
+ throw new NotSupportedException($"Unknown property({propertyName}) found.");
+ }
+ }
+
+ if (reader.TokenType != JsonTokenType.EndObject)
+ throw new JsonException("Invalid JSON");
+
using var disassembler = CapstoneDisassembler.CreateArm64Disassembler(Arm64DisassembleMode.Arm);
disassembler.EnableInstructionDetails = true;
- disassembler.DisassembleSyntax = (DisassembleSyntax)Convert.ToInt32(json[SyntaxKey]);
- byte[] bytes = Convert.FromBase64String((string)bytes64);
- Instruction = disassembler.Disassemble(bytes, long.Parse((string)json[AddressKey])).Single();
+ disassembler.DisassembleSyntax = syntax;
+ var instruction = disassembler.Disassemble(instructionBytes, instructionAddress).SingleOrDefault();
+
+ return new Arm64AsmData
+ {
+ DisassembleSyntax = syntax,
+ Instruction = instruction,
+ };
+ }
+
+ public override void Write(Utf8JsonWriter writer, Arm64AsmData value, JsonSerializerOptions options)
+ {
+ writer.WriteStartObject();
+ writer.WriteString(Arm64SyntaxKey, value.DisassembleSyntax.ToString());
+ var instruction = value.Instruction;
+ if (instruction != null)
+ {
+ writer.WriteNumber(Arm64AddressKey, instruction.Address);
+ writer.WriteBase64String(Arm64BytesKey, instruction.Bytes);
+ }
+ writer.WriteEndObject();
}
}
+
}
public sealed class MonoCode : SourceCode
{
public string Text { get; set; } = "";
-
- private protected override void Serialize(JsonObject json)
- {
- base.Serialize(json);
-
- json[nameof(Text)] = Text;
- }
-
- internal override void Deserialize(JsonObject json)
- {
- base.Deserialize(json);
-
- Text = (string) json[nameof(Text)];
- }
}
public sealed class Map
{
- [XmlArray("Instructions")]
- [XmlArrayItem(nameof(SourceCode), typeof(SourceCode))]
- [XmlArrayItem(nameof(Sharp), typeof(Sharp))]
- [XmlArrayItem(nameof(IntelAsm), typeof(IntelAsm))]
public SourceCode[] SourceCodes { get; set; } = [];
-
- internal JsonObject Serialize()
- {
- var sourceCodes = new JsonArray(SourceCodes.Length);
- foreach (var sourceCode in SourceCodes)
- {
- sourceCodes.Add(sourceCode.Serialize());
- }
- return new JsonObject
- {
- [nameof(SourceCodes)] = sourceCodes,
- };
- }
-
- internal void Deserialize(JsonObject json)
- {
- var sourceCodes = (JsonArray) json[nameof(SourceCodes)];
- SourceCodes = new SourceCode[sourceCodes.Count];
- for (int i = 0; i < sourceCodes.Count; i++)
- {
- var sourceJson = (JsonObject) sourceCodes[i];
- SourceCodes[i] = sourceJson["$type"] switch
- {
- nameof(Sharp) => new Sharp(),
- nameof(IntelAsm) => new IntelAsm(),
- nameof(Arm64Asm) => new Arm64Asm(),
- nameof(MonoCode) => new MonoCode(),
- var unhandledType => throw new NotSupportedException($"Unexpected type: {unhandledType}")
- };
- SourceCodes[i].Deserialize(sourceJson);
- }
- }
}
public sealed class DisassembledMethod
@@ -282,121 +205,16 @@ public static DisassembledMethod Empty(string fullSignature, ulong nativeCode, s
NativeCode = nativeCode,
Problem = problem
};
-
- internal JsonObject Serialize()
- {
- var maps = new JsonArray(Maps.Length);
- foreach (var map in Maps)
- {
- maps.Add(map.Serialize());
- }
- return new JsonObject
- {
- [nameof(Name)] = Name,
- [nameof(NativeCode)] = NativeCode.ToString(),
- [nameof(Problem)] = Problem,
- [nameof(Maps)] = maps,
- [nameof(CommandLine)] = CommandLine
- };
- }
-
- internal void Deserialize(JsonObject json)
- {
- Name = (string) json[nameof(Name)];
- NativeCode = ulong.Parse((string) json[nameof(NativeCode)]);
- Problem = (string) json[nameof(Problem)];
-
- var maps = (JsonArray) json[nameof(Maps)];
- Maps = new Map[maps.Count];
- for (int i = 0; i < maps.Count; i++)
- {
- Maps[i] = new Map();
- Maps[i].Deserialize((JsonObject) maps[i]);
- }
-
- CommandLine = (string) json[nameof(CommandLine)];
- }
}
public sealed class DisassemblyResult
{
public DisassembledMethod[] Methods { get; set; } = [];
public string[] Errors { get; set; } = [];
- public MutablePair[] SerializedAddressToNameMapping { get; set; } = [];
- public uint PointerSize { get; set; }
-
- [XmlIgnore] // XmlSerializer does not support dictionaries ;)
- public Dictionary AddressToNameMapping
- => _addressToNameMapping ??= SerializedAddressToNameMapping.ToDictionary(x => x.Key, x => x.Value);
- [XmlIgnore]
- private Dictionary? _addressToNameMapping = null;
-
- // KeyValuePair is not serializable, because it has read-only properties
- // so we need to define our own...
- [Serializable]
- [XmlType(TypeName = "Workaround")]
- public struct MutablePair
- {
- public ulong Key { get; set; }
- public string Value { get; set; }
- }
-
- internal JsonObject Serialize()
- {
- var methods = new JsonArray(Methods.Length);
- foreach (var method in Methods)
- {
- methods.Add(method.Serialize());
- }
- var errors = new JsonArray(Errors.Length);
- foreach (var error in Errors)
- {
- errors.Add(error);
- }
- var addressToNameMapping = new JsonObject();
- foreach (var kvp in SerializedAddressToNameMapping)
- {
- addressToNameMapping[kvp.Key.ToString()] = kvp.Value;
- }
- return new JsonObject
- {
- [nameof(Methods)] = methods,
- [nameof(Errors)] = errors,
- [nameof(AddressToNameMapping)] = addressToNameMapping,
- [nameof(PointerSize)] = PointerSize.ToString()
- };
- }
-
- internal void Deserialize(JsonObject json)
- {
- var methods = (JsonArray) json[nameof(Methods)];
- Methods = new DisassembledMethod[methods.Count];
- for (int i = 0; i < methods.Count; i++)
- {
- Methods[i] = new DisassembledMethod();
- Methods[i].Deserialize((JsonObject) methods[i]);
- }
-
- var errors = (JsonArray) json[nameof(Errors)];
- Errors = new string[errors.Count];
- for (int i = 0; i < errors.Count; i++)
- {
- Errors[i] = (string) errors[i];
- }
-
- var addressToNameMapping = (JsonObject) json[nameof(AddressToNameMapping)];
- SerializedAddressToNameMapping = new MutablePair[addressToNameMapping.Count];
- int addressIndex = 0;
- foreach (var kvp in addressToNameMapping)
- {
- SerializedAddressToNameMapping[addressIndex].Key = ulong.Parse(kvp.Key);
- SerializedAddressToNameMapping[addressIndex].Value = (string) kvp.Value;
- ++addressIndex;
- }
+ public uint PointerSize { get; set; }
- PointerSize = uint.Parse((string) json[nameof(PointerSize)]);
- }
+ public Dictionary AddressToNameMapping { get; set; } = [];
}
public static class DisassemblerConstants
@@ -459,7 +277,7 @@ public bool Equals(ClrMethod? x, ClrMethod? y)
return x.NativeCode == y.NativeCode;
}
- public int GetHashCode(ClrMethod obj) => (int) obj.NativeCode;
+ public int GetHashCode(ClrMethod obj) => (int)obj.NativeCode;
}
}
diff --git a/src/BenchmarkDotNet/Disassemblers/DisassemblyDiagnoser.cs b/src/BenchmarkDotNet/Disassemblers/DisassemblyDiagnoser.cs
index 80c404f5d1..cafa3d04de 100644
--- a/src/BenchmarkDotNet/Disassemblers/DisassemblyDiagnoser.cs
+++ b/src/BenchmarkDotNet/Disassemblers/DisassemblyDiagnoser.cs
@@ -1,9 +1,4 @@
-using System;
-using System.Collections.Generic;
-using System.ComponentModel;
-using System.Diagnostics;
-using System.Linq;
-using BenchmarkDotNet.Analysers;
+using BenchmarkDotNet.Analysers;
using BenchmarkDotNet.Columns;
using BenchmarkDotNet.Detectors;
using BenchmarkDotNet.Disassemblers;
@@ -11,17 +6,23 @@
using BenchmarkDotNet.Engines;
using BenchmarkDotNet.Environments;
using BenchmarkDotNet.Exporters;
+using BenchmarkDotNet.Extensions;
using BenchmarkDotNet.Helpers;
using BenchmarkDotNet.Jobs;
using BenchmarkDotNet.Loggers;
using BenchmarkDotNet.Portability;
using BenchmarkDotNet.Reports;
using BenchmarkDotNet.Running;
+using BenchmarkDotNet.Serialization;
using BenchmarkDotNet.Toolchains.InProcess.NoEmit;
using BenchmarkDotNet.Validators;
using JetBrains.Annotations;
using Perfolizer.Metrology;
-using SimpleJson;
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Diagnostics;
+using System.Linq;
#nullable enable
@@ -234,15 +235,15 @@ InProcessDiagnoserHandlerData IInProcessDiagnoser.GetHandlerData(BenchmarkCase b
{
return default;
}
- return new(typeof(DisassemblyDiagnoserInProcessHandler), BuildClrMdArgs(benchmarkCase, "", 0).Serialize());
+
+ var clrMdArgs = BuildClrMdArgs(benchmarkCase, "", 0);
+ return new(typeof(DisassemblyDiagnoserInProcessHandler), BdnJsonSerializer.Serialize(clrMdArgs));
}
void IInProcessDiagnoser.DeserializeResults(BenchmarkCase benchmarkCase, string results)
{
- var json = SimpleJsonSerializer.DeserializeObject(results);
- var result = new DisassemblyResult();
- result.Deserialize(json);
- this.results.Add(benchmarkCase, result);
+ var disassemblyResult = BdnJsonSerializer.Deserialize(results);
+ this.results.Add(benchmarkCase, disassemblyResult);
}
private class NativeCodeSizeMetricDescriptor : IMetricDescriptor
@@ -270,7 +271,7 @@ public sealed class DisassemblyDiagnoserInProcessHandler : IInProcessDiagnoserHa
void IInProcessDiagnoserHandler.Initialize(string? serializedConfig)
{
- _clrMdArgs.Deserialize(serializedConfig);
+ _clrMdArgs = BdnJsonSerializer.Deserialize(serializedConfig!);
}
void IInProcessDiagnoserHandler.Handle(BenchmarkSignal signal, InProcessDiagnoserActionArgs args)
@@ -288,8 +289,7 @@ void IInProcessDiagnoserHandler.Handle(BenchmarkSignal signal, InProcessDiagnose
string IInProcessDiagnoserHandler.SerializeResults()
{
- SimpleJsonSerializer.CurrentJsonSerializerStrategy.Indent = false;
- return _result.Serialize().ToString();
+ return BdnJsonSerializer.Serialize(_result);
}
}
}
\ No newline at end of file
diff --git a/src/BenchmarkDotNet/Serialization/BdnJsonSerializer.cs b/src/BenchmarkDotNet/Serialization/BdnJsonSerializer.cs
new file mode 100644
index 0000000000..8ea6ddba04
--- /dev/null
+++ b/src/BenchmarkDotNet/Serialization/BdnJsonSerializer.cs
@@ -0,0 +1,47 @@
+using System.Text.Encodings.Web;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+
+namespace BenchmarkDotNet.Serialization;
+
+internal static class BdnJsonSerializer
+{
+ private static readonly JsonSerializerOptions DefaultOptions = new()
+ {
+ Converters =
+ {
+ new JsonStringEnumConverter(),
+ },
+ DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
+ DictionaryKeyPolicy = JsonNamingPolicy.CamelCase,
+ // Disable escaping non ASCII chars. https://learn.microsoft.com/en-us/dotnet/standard/serialization/system-text-json/character-encoding#serialize-all-characters
+ Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, // TODO: Replace to custom encoder that escape minimal chars. (https://github.com/dotnet/runtime/issues/87153)
+ NumberHandling = JsonNumberHandling.AllowNamedFloatingPointLiterals, // Required to support NaN
+ PropertyNameCaseInsensitive = true, // Accept PascalNaming that generated by JsonExporter.
+ PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
+ RespectNullableAnnotations = true,
+ TypeInfoResolverChain =
+ {
+ BdnJsonSerializerContext.Default,
+ },
+ UnmappedMemberHandling = JsonUnmappedMemberHandling.Disallow, // Throw error if unknown entry exists.
+ };
+
+ private static readonly JsonSerializerOptions IndentedOptions = new(DefaultOptions)
+ {
+ WriteIndented = true,
+ };
+
+ public static string Serialize(T item, bool indentJson = false)
+ {
+ if (indentJson)
+ return JsonSerializer.Serialize(item, IndentedOptions);
+ else
+ return JsonSerializer.Serialize(item, DefaultOptions);
+ }
+
+ public static T Deserialize(string json)
+ {
+ return JsonSerializer.Deserialize(json, DefaultOptions);
+ }
+}
diff --git a/src/BenchmarkDotNet/Serialization/BdnJsonSerializerContext.cs b/src/BenchmarkDotNet/Serialization/BdnJsonSerializerContext.cs
new file mode 100644
index 0000000000..3a3c2ff2e1
--- /dev/null
+++ b/src/BenchmarkDotNet/Serialization/BdnJsonSerializerContext.cs
@@ -0,0 +1,16 @@
+using BenchmarkDotNet.Disassemblers;
+using System.Text.Json.Serialization;
+
+namespace BenchmarkDotNet.Serialization;
+
+[JsonSerializable(typeof(ClrMdArgs))]
+[JsonSerializable(typeof(Sharp))]
+[JsonSerializable(typeof(MonoCode))]
+[JsonSerializable(typeof(IntelAsm))]
+[JsonSerializable(typeof(Arm64Asm))]
+[JsonSerializable(typeof(Map))]
+[JsonSerializable(typeof(DisassembledMethod))]
+[JsonSerializable(typeof(DisassemblyResult))]
+internal partial class BdnJsonSerializerContext : JsonSerializerContext
+{
+}
diff --git a/tests/BenchmarkDotNet.IntegrationTests/LargeAddressAwareTest.cs b/tests/BenchmarkDotNet.IntegrationTests/LargeAddressAwareTest.cs
index 2fb27498cd..66a056d063 100644
--- a/tests/BenchmarkDotNet.IntegrationTests/LargeAddressAwareTest.cs
+++ b/tests/BenchmarkDotNet.IntegrationTests/LargeAddressAwareTest.cs
@@ -24,7 +24,7 @@ public class LargeAddressAwareTest
public void BenchmarkCanAllocateMoreThan2Gb_Core()
{
var platform = RuntimeInformation.GetCurrentPlatform();
- var config = ManualConfig.CreateEmpty();
+ var config = ManualConfig.CreateEmpty().WithBuildTimeout(TimeSpan.FromSeconds(240));
// Running 32-bit benchmarks with .Net Core requires passing the path to 32-bit SDK,
// which makes this test more complex than it's worth in CI, so we only test 64-bit.
config.AddJob(Job.Dry.WithRuntime(CoreRuntime.Core80).WithPlatform(platform).WithId(platform.ToString()));
diff --git a/tests/BenchmarkDotNet.Tests/BenchmarkDotNet.Tests.csproj b/tests/BenchmarkDotNet.Tests/BenchmarkDotNet.Tests.csproj
index ea90fe0e89..0e21034d6c 100755
--- a/tests/BenchmarkDotNet.Tests/BenchmarkDotNet.Tests.csproj
+++ b/tests/BenchmarkDotNet.Tests/BenchmarkDotNet.Tests.csproj
@@ -15,6 +15,7 @@
+
diff --git a/tests/BenchmarkDotNet.Tests/Serialization/DisassemblerModelSerializationTests.cs b/tests/BenchmarkDotNet.Tests/Serialization/DisassemblerModelSerializationTests.cs
new file mode 100644
index 0000000000..b305be975c
--- /dev/null
+++ b/tests/BenchmarkDotNet.Tests/Serialization/DisassemblerModelSerializationTests.cs
@@ -0,0 +1,261 @@
+using AwesomeAssertions;
+using BenchmarkDotNet.Disassemblers;
+using BenchmarkDotNet.Serialization;
+using BenchmarkDotNet.Tests.XUnit;
+using Gee.External.Capstone;
+using Gee.External.Capstone.Arm64;
+using Iced.Intel;
+using System.Linq;
+using Xunit;
+
+namespace BenchmarkDotNet.Tests;
+
+public class DisassemblerModelSerializationTests
+{
+ [Fact]
+ public void ClrMdArgsSerializationTest()
+ {
+ // Arrange
+ var model = new ClrMdArgs(
+ processId: 100, // ProcessID field is not serialized/deserialized.
+ typeName: "TypeName", // TypeName field is not serialized/deserialized.
+ methodName: "MethodName",
+ printSource: true,
+ maxDepth: 5,
+ syntax: "Syntax",
+ tfm: "Tfm",
+ filters: ["filter1", "filter2"],
+ resultsPath: "/path/to/results"
+ );
+
+ // Act
+ var json = BdnJsonSerializer.Serialize(model);
+ var result = BdnJsonSerializer.Deserialize(json);
+
+ // Assert
+ json.Should().NotBe("{}");
+ result.Should().BeEquivalentTo(
+ model,
+ options => options
+ .IncludingInternalFields()
+ .ComparingByMembers(typeof(ClrMdArgs)) // Required to use Excluding for struct type. See: https://github.com/fluentassertions/fluentassertions/issues/937
+ .Excluding(x => x.ProcessId)
+ .Excluding(x => x.TypeName));
+ }
+
+ [Fact]
+ public void SharpSerializationTest()
+ {
+ var model = new Sharp
+ {
+ InstructionPointer = 1,
+ Text = "Text",
+ FilePath = "FilePath",
+ LineNumber = 2,
+ };
+
+ // Act
+ var json = BdnJsonSerializer.Serialize(model);
+ var result = BdnJsonSerializer.Deserialize(json);
+
+ // Assert
+ json.Should().NotBe("{}");
+ result.Should().BeEquivalentTo(model);
+ }
+
+ [Fact]
+ public void MonoCodeSerializationTest()
+ {
+ // Arrange
+ var model = new MonoCode
+ {
+ InstructionPointer = 1,
+ Text = "Text",
+ };
+
+ // Act
+ var json = BdnJsonSerializer.Serialize(model);
+ var result = BdnJsonSerializer.Deserialize(json);
+
+ // Assert
+ json.Should().NotBe("{}");
+ result.Should().BeEquivalentTo(model);
+ }
+
+ [Fact]
+ public void IntelAsmSerializationTest()
+ {
+ // Arrange
+ var model = new IntelAsm
+ {
+ InstructionPointer = 1,
+ InstructionLength = 2,
+ ReferencedAddress = 3,
+ IsReferencedAddressIndirect = true,
+ Instruction = Instruction.Create(
+ Iced.Intel.Code.Xlat_m8,
+ new MemoryOperand(Register.RBX, Register.AL)
+ ),
+ };
+
+ // Act
+ var json = BdnJsonSerializer.Serialize(model);
+ var result = BdnJsonSerializer.Deserialize(json);
+
+ // Assert
+ Assert.NotEqual("{}", json);
+ Assert.Equivalent(model, result, strict: true);
+ }
+
+ [FactEnvSpecific("ARM64 disassembler is not supported on .NET Framework or Windows+Arm environment", EnvRequirement.NonFullFramework, EnvRequirement.NonWindowsArm)]
+ public void Arm64AsmSerializationTest()
+ {
+ // Arrange
+ byte[] instructionBytes = [0xE1, 0x0B, 0x40, 0xB9]; // ldr w1, [sp, #8]
+ var disassembleSyntax = DisassembleSyntax.Intel;
+
+ // Create instruction instance by using disassembler.
+ using var disassembler = CapstoneDisassembler.CreateArm64Disassembler(Arm64DisassembleMode.Arm);
+ disassembler.EnableInstructionDetails = true;
+ disassembler.DisassembleSyntax = disassembleSyntax;
+
+ // Act
+ var instructions = disassembler.Disassemble(instructionBytes);
+ var instruction = instructions.Single();
+
+ var model = new Arm64Asm
+ {
+ DisassembleSyntax = disassembleSyntax,
+ Instruction = instruction,
+ InstructionLength = instruction.Bytes.Length,
+ InstructionPointer = (ulong)instruction.Address,
+ ReferencedAddress = (instruction.Address > ushort.MaxValue) ? (ulong)instruction.Address : null,
+ IsReferencedAddressIndirect = true, // Test with dummy value
+ };
+
+ // Act
+ var json = BdnJsonSerializer.Serialize(model);
+ var result = BdnJsonSerializer.Deserialize(json);
+
+ // Assert
+ json.Should().NotBe("{}");
+
+ // Compare properties (Except for`Instruction.Details.Operands` property that )
+ result.Instruction.ToString().Should().Be("ldr w1, [sp, #8]");
+
+ result.Should()
+ .BeEquivalentTo(model, options => options.Excluding(x => x.Instruction.Details.Operands));
+ }
+
+ [Fact]
+ public void MapSerializationTest()
+ {
+ // Arrange
+ var model = new Map
+ {
+ SourceCodes =
+ [
+ new MonoCode
+ {
+ Text = "MonoCodeText1",
+ InstructionPointer = 1,
+ },
+ new Sharp
+ {
+ Text = "SharpText" ,
+ FilePath ="FilePath",
+ LineNumber = 1,
+ InstructionPointer = 2,
+ },
+ new MonoCode {
+ Text = "MonoCodeText2",
+ InstructionPointer = 2,
+ },
+ ]
+ };
+
+ // Act
+ var json = BdnJsonSerializer.Serialize(model);
+ var result = BdnJsonSerializer.Deserialize