Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions src/BenchmarkDotNet/BenchmarkDotNet.targets
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,16 @@
Condition="'%(RuntimeTargetsCopyLocalItems.NuGetPackageId)' == 'Gee.External.Capstone' AND '%(RuntimeTargetsCopyLocalItems.RuntimeIdentifier)' != '$(BenchmarkDotNetTargetPlatform)'" />
</ItemGroup>

<!--
The SDK copies `runtimes/{RuntimeIdentifier}` assets only for .NETCoreApp.
Copy the native capstone for .NET Framework too, where the Arm64 disassembler loads it from that folder itself.
-->
<ItemGroup Condition="'$(TargetFrameworkIdentifier)' != '.NETCoreApp'">
<RuntimeTargetsCopyLocalItems Condition="'%(RuntimeTargetsCopyLocalItems.NuGetPackageId)' == 'Gee.External.Capstone'">
<CopyLocal>true</CopyLocal>
</RuntimeTargetsCopyLocalItems>
</ItemGroup>

<!-- Remove unnecessary satellite assemblies. -->
<ItemGroup>
<ResourceCopyLocalItems
Expand Down
2 changes: 1 addition & 1 deletion src/BenchmarkDotNet/Disassemblers/Arm64Disassembler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ internal class Arm64Disassembler : ClrMdDisassembler
protected override IEnumerable<Asm> Decode(byte[] code, ulong startAddress, State state, int depth, IClrMethod currentMethod, DisassemblySyntax syntax)
{
const Arm64DisassembleMode disassembleMode = Arm64DisassembleMode.Arm;
using (CapstoneArm64Disassembler disassembler = CapstoneDisassembler.CreateArm64Disassembler(disassembleMode))
using (CapstoneArm64Disassembler disassembler = CapstoneFactory.CreateArm64Disassembler(disassembleMode))
{
// Enables disassemble details, which are disabled by default, to provide more detailed information on
// disassembled binary code.
Expand Down
58 changes: 58 additions & 0 deletions src/BenchmarkDotNet/Disassemblers/CapstoneFactory.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
using Gee.External.Capstone;
using Gee.External.Capstone.Arm64;
#if !NET
using BenchmarkDotNet.Environments;
using BenchmarkDotNet.Extensions;
using BenchmarkDotNet.Helpers;
using System.Runtime.InteropServices;
using RuntimeInformation = BenchmarkDotNet.Portability.RuntimeInformation;
#endif

namespace BenchmarkDotNet.Disassemblers;

internal static class CapstoneFactory
{
internal static CapstoneArm64Disassembler CreateArm64Disassembler(Arm64DisassembleMode disassembleMode)
{
#if !NET
_ = FrameworkNativeLibrary.Handle.Value;
#endif
return CapstoneDisassembler.CreateArm64Disassembler(disassembleMode);
}

#if !NET
/// <summary>
/// .NET Framework does not probe <c>runtimes/{rid}/native</c>, where BenchmarkDotNet.targets copies the native capstone.
/// Once it is loaded by its full path, the P/Invokes that name the module bind to the loaded library.
/// When the file is not there, the default search applies - a build for a specific RuntimeIdentifier puts it next to the application.
/// </summary>
private static class FrameworkNativeLibrary
{
internal static readonly Lazy<IntPtr> Handle = new(Load);

private static IntPtr Load()
{
if (!RuntimeInformation.IsFullFramework)
return IntPtr.Zero;

string? rid = RuntimeInformation.GetCurrentPlatform() switch
{
Platform.X86 => "win-x86",
Platform.X64 => "win-x64",
Platform.Arm64 => "win-arm64",
_ => null
};
var assembly = typeof(CapstoneDisassembler).Assembly;
string location = ShadowCopyHelper.TryGetOriginalLocation(assembly, out var originalLocation) ? originalLocation : assembly.Location;
if (rid is null || location.IsBlank())
return IntPtr.Zero;

string path = Path.Combine(Path.GetDirectoryName(location)!, "runtimes", rid, "native", "capstone.dll");
return File.Exists(path) ? LoadLibraryW(path) : IntPtr.Zero;
}

[DllImport("kernel32", ExactSpelling = true, CharSet = CharSet.Unicode)]
private static extern IntPtr LoadLibraryW(string lpLibFileName);
}
#endif
}
2 changes: 1 addition & 1 deletion src/BenchmarkDotNet/Disassemblers/DataContracts.cs
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ public override Arm64AsmData Read(ref Utf8JsonReader reader, Type typeToConvert,
if (reader.TokenType != JsonTokenType.EndObject)
throw new JsonException("Invalid JSON");

using var disassembler = CapstoneDisassembler.CreateArm64Disassembler(Arm64DisassembleMode.Arm);
using var disassembler = CapstoneFactory.CreateArm64Disassembler(Arm64DisassembleMode.Arm);
disassembler.EnableInstructionDetails = true;
disassembler.DisassembleSyntax = syntax;
var instruction = disassembler.Disassemble(instructionBytes, instructionAddress).SingleOrDefault();
Expand Down
6 changes: 0 additions & 6 deletions src/BenchmarkDotNet/Disassemblers/DisassemblyDiagnoser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -121,12 +121,6 @@ public async IAsyncEnumerable<ValidationError> ValidateAsync(ValidationParameter
yield break;
}

if (currentPlatform == Platform.Arm64 && OsDetector.IsWindows())
{
yield return new ValidationError(true, $"{nameof(DisassemblyDiagnoser)} does not support Arm on Windows");
yield break;
}

if (Config.RunInHost && OsDetector.IsMacOS())
{
yield return new ValidationError(true, "Disassembling in the host process is not supported on MacOS");
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Columns;
using BenchmarkDotNet.Configs;
using BenchmarkDotNet.Detectors;
Expand All @@ -10,7 +10,6 @@
using BenchmarkDotNet.Jobs;
using BenchmarkDotNet.Portability;
using BenchmarkDotNet.Tests.Loggers;
using BenchmarkDotNet.Tests.XUnit;
using BenchmarkDotNet.Toolchains;
using BenchmarkDotNet.Toolchains.Framework;
using BenchmarkDotNet.Toolchains.InProcess.Emit;
Expand All @@ -32,6 +31,12 @@ public static IEnumerable<object[]> GetAllJits()

if (RuntimeInformation.IsFullFramework)
{
if (RuntimeInformation.GetCurrentPlatform() is Platform.Arm64)
{
// RyuJit for desktop .NET arm64. Supported only on net481, but we have to match the tfm in our test project.
yield return [Jit.RyuJit, Platform.Arm64, CsProjFrameworkToolchain.Net472];
}
// Framework on arm emulates x86, so these platform targets should work on both.
yield return [Jit.LegacyJit, Platform.X86, CsProjFrameworkToolchain.Net472]; // 32bit LegacyJit for desktop .NET
yield return [Jit.LegacyJit, Platform.X64, CsProjFrameworkToolchain.Net472]; // 64bit LegacyJit for desktop .NET
yield return [Jit.RyuJit, Platform.X64, CsProjFrameworkToolchain.Net472]; // RyuJit for desktop .NET
Expand Down Expand Up @@ -87,7 +92,7 @@ public void Recursive()
[MethodImpl(MethodImplOptions.NoInlining)] public void Benchmark(bool justAnOverload) { } // we need to test overloads (#562)
}

[TheoryEnvSpecific("Not supported on Windows+Arm", EnvRequirement.NonWindowsArm)]
[Theory]
[MemberData(nameof(GetAllJits), DisableDiscoveryEnumeration = true)]
[Trait(Constants.Category, Constants.BackwardCompatibilityCategory)]
public void CanDisassembleAllMethodCalls(Jit jit, Platform platform, IToolchain toolchain)
Expand All @@ -107,7 +112,7 @@ public void CanDisassembleAllMethodCalls(Jit jit, Platform platform, IToolchain
AssertDisassemblyResult(result, $"{nameof(WithCalls.Recursive)}()");
}

[TheoryEnvSpecific("Not supported on Windows+Arm", EnvRequirement.NonWindowsArm)]
[Theory]
[MemberData(nameof(GetAllJits), DisableDiscoveryEnumeration = true)]
[Trait(Constants.Category, Constants.BackwardCompatibilityCategory)]
public void CanDisassembleAllMethodCallsUsingFilters(Jit jit, Platform platform, IToolchain toolchain)
Expand All @@ -133,7 +138,7 @@ public void CanDisassembleAllMethodCallsUsingFilters(Jit jit, Platform platform,
public T Create() => new T();
}

[TheoryEnvSpecific("Not supported on Windows+Arm", EnvRequirement.NonWindowsArm)]
[Theory]
[MemberData(nameof(GetAllJits), DisableDiscoveryEnumeration = true)]
[Trait(Constants.Category, Constants.BackwardCompatibilityCategory)]
public void CanDisassembleGenericTypes(Jit jit, Platform platform, IToolchain toolchain)
Expand All @@ -154,7 +159,7 @@ public class WithInlineable
[Benchmark] public void JustReturn() { }
}

[TheoryEnvSpecific("Not supported on Windows+Arm", EnvRequirement.NonWindowsArm)]
[Theory]
[MemberData(nameof(GetAllJits), DisableDiscoveryEnumeration = true)]
[Trait(Constants.Category, Constants.BackwardCompatibilityCategory)]
public void CanDisassembleInlinableBenchmarks(Jit jit, Platform platform, IToolchain toolchain)
Expand Down Expand Up @@ -188,7 +193,7 @@ public class WithGeneratedMemberNames
[MethodImpl(MethodImplOptions.NoInlining)] public virtual void ForDisassemblyDiagnoser() { }
}

[TheoryEnvSpecific("Not supported on Windows+Arm", EnvRequirement.NonWindowsArm)]
[Theory]
[MemberData(nameof(GetAllJits), DisableDiscoveryEnumeration = true)]
[Trait(Constants.Category, Constants.BackwardCompatibilityCategory)]
public void CanDisassembleWhenBenchmarkDeclaresGeneratedMemberNames(Jit jit, Platform platform, IToolchain toolchain)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
using AwesomeAssertions;
using BenchmarkDotNet.Disassemblers;
using BenchmarkDotNet.Serialization;
using BenchmarkDotNet.Tests.XUnit;
using Gee.External.Capstone;
using Gee.External.Capstone.Arm64;
using Iced.Intel;
Expand Down Expand Up @@ -105,15 +104,15 @@ public void IntelAsmSerializationTest()
Assert.Equivalent(model, result, strict: true);
}

[FactEnvSpecific("ARM64 disassembler is not supported on .NET Framework or Windows+Arm environment", EnvRequirement.NonFullFramework, EnvRequirement.NonWindowsArm)]
[Fact]
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);
using var disassembler = CapstoneFactory.CreateArm64Disassembler(Arm64DisassembleMode.Arm);
disassembler.EnableInstructionDetails = true;
disassembler.DisassembleSyntax = disassembleSyntax;

Expand Down
Loading