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
115 changes: 111 additions & 4 deletions src/ImageSharp/Compression/Zlib/Crc32.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@
#if SUPPORTS_RUNTIME_INTRINSICS
using System.Runtime.Intrinsics;
using System.Runtime.Intrinsics.X86;
#if NET5_0_OR_GREATER
using ArmCrc32 = System.Runtime.Intrinsics.Arm.Crc32;
#endif
#endif

namespace SixLabors.ImageSharp.Compression.Zlib
Expand Down Expand Up @@ -65,13 +68,20 @@ public static uint Calculate(uint crc, ReadOnlySpan<byte> buffer)
{
return ~CalculateSse(~crc, buffer);
}
else

#if NET5_0_OR_GREATER
if (ArmCrc32.Arm64.IsSupported)
{
return ~CalculateScalar(~crc, buffer);
return ~CalculateArm64(~crc, buffer);
}
#else
return ~CalculateScalar(~crc, buffer);

if (ArmCrc32.IsSupported)
{
return ~CalculateArm(~crc, buffer);
}
#endif
#endif
return ~CalculateScalar(~crc, buffer);
}

#if SUPPORTS_RUNTIME_INTRINSICS
Expand Down Expand Up @@ -198,6 +208,103 @@ private static unsafe uint CalculateSse(uint crc, ReadOnlySpan<byte> buffer)
}
}
}

#if NET5_0_OR_GREATER
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We'll need to actually target a .NET 5+ runtime to take advantage of this. I want to add an explicit target for .NET 6 for v3.0.0


[MethodImpl(InliningOptions.HotPath | InliningOptions.ShortMethod)]
private static unsafe uint CalculateArm(uint crc, ReadOnlySpan<byte> buffer)
{
fixed (byte* bufferPtr = buffer)
{
byte* localBufferPtr = bufferPtr;
int len = buffer.Length;

while (len > 0 && ((ulong)localBufferPtr & 3) != 0)
{
crc = ArmCrc32.ComputeCrc32(crc, *localBufferPtr++);
len--;
}

uint* intBufferPtr = (uint*)localBufferPtr;

while (len >= 8 * sizeof(uint))
{
crc = ArmCrc32.ComputeCrc32(crc, *intBufferPtr++);
crc = ArmCrc32.ComputeCrc32(crc, *intBufferPtr++);
crc = ArmCrc32.ComputeCrc32(crc, *intBufferPtr++);
crc = ArmCrc32.ComputeCrc32(crc, *intBufferPtr++);
crc = ArmCrc32.ComputeCrc32(crc, *intBufferPtr++);
crc = ArmCrc32.ComputeCrc32(crc, *intBufferPtr++);
crc = ArmCrc32.ComputeCrc32(crc, *intBufferPtr++);
crc = ArmCrc32.ComputeCrc32(crc, *intBufferPtr++);
len -= 8 * sizeof(uint);
}

while (len >= sizeof(uint))
{
crc = ArmCrc32.ComputeCrc32(crc, *intBufferPtr++);
len -= sizeof(uint);
}

localBufferPtr = (byte*)intBufferPtr;

while (len > 0)
{
crc = ArmCrc32.ComputeCrc32(crc, *localBufferPtr++);
len--;
}

return crc;
}
}

[MethodImpl(InliningOptions.HotPath | InliningOptions.ShortMethod)]
private static unsafe uint CalculateArm64(uint crc, ReadOnlySpan<byte> buffer)
{
fixed (byte* bufferPtr = buffer)
{
byte* localBufferPtr = bufferPtr;
int len = buffer.Length;

while (len > 0 && ((ulong)localBufferPtr & 7) != 0)
{
crc = ArmCrc32.ComputeCrc32(crc, *localBufferPtr++);
len--;
}

ulong* longBufferPtr = (ulong*)localBufferPtr;

while (len >= 8 * sizeof(ulong))
{
crc = ArmCrc32.Arm64.ComputeCrc32(crc, *longBufferPtr++);
crc = ArmCrc32.Arm64.ComputeCrc32(crc, *longBufferPtr++);
crc = ArmCrc32.Arm64.ComputeCrc32(crc, *longBufferPtr++);
crc = ArmCrc32.Arm64.ComputeCrc32(crc, *longBufferPtr++);
crc = ArmCrc32.Arm64.ComputeCrc32(crc, *longBufferPtr++);
crc = ArmCrc32.Arm64.ComputeCrc32(crc, *longBufferPtr++);
crc = ArmCrc32.Arm64.ComputeCrc32(crc, *longBufferPtr++);
crc = ArmCrc32.Arm64.ComputeCrc32(crc, *longBufferPtr++);
len -= 8 * sizeof(ulong);
}

while (len >= sizeof(ulong))
{
crc = ArmCrc32.Arm64.ComputeCrc32(crc, *longBufferPtr++);
len -= sizeof(ulong);
}

localBufferPtr = (byte*)longBufferPtr;

while (len > 0)
{
crc = ArmCrc32.ComputeCrc32(crc, *localBufferPtr++);
len--;
}

return crc;
}
}
#endif
#endif

[MethodImpl(InliningOptions.HotPath | InliningOptions.ShortMethod)]
Expand Down
36 changes: 28 additions & 8 deletions tests/ImageSharp.Tests/Formats/Png/Crc32Tests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

using System;
using SixLabors.ImageSharp.Compression.Zlib;
using SixLabors.ImageSharp.Tests.TestUtilities;
using Xunit;
using SharpCrc32 = ICSharpCode.SharpZipLib.Checksum.Crc32;

Expand All @@ -15,10 +16,7 @@ public class Crc32Tests
[InlineData(0)]
[InlineData(1)]
[InlineData(2)]
public void ReturnsCorrectWhenEmpty(uint input)
{
Assert.Equal(input, Crc32.Calculate(input, default));
}
public void CalculateCrc_ReturnsCorrectResultWhenEmpty(uint input) => Assert.Equal(input, Crc32.Calculate(input, default));

[Theory]
[InlineData(0)]
Expand All @@ -28,24 +26,46 @@ public void ReturnsCorrectWhenEmpty(uint input)
[InlineData(1024 + 15)]
[InlineData(2034)]
[InlineData(4096)]
public void MatchesReference(int length)
public void CalculateCrc_MatchesReference(int length) => CalculateCrcAndCompareToReference(length);

private static void CalculateCrcAndCompareToReference(int length)
{
var data = GetBuffer(length);
// arrange
byte[] data = GetBuffer(length);
var crc = new SharpCrc32();
crc.Update(data);

long expected = crc.Value;

// act
long actual = Crc32.Calculate(data);

// assert
Assert.Equal(expected, actual);
}

private static byte[] GetBuffer(int length)
{
var data = new byte[length];
byte[] data = new byte[length];
new Random(1).NextBytes(data);

return data;
}

#if SUPPORTS_RUNTIME_INTRINSICS
[Fact]
public void RunCalculateCrcTest_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunCalculateCrcTest, HwIntrinsics.AllowAll);

[Fact]
public void RunCalculateCrcTest_WithoutHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunCalculateCrcTest, HwIntrinsics.DisableHWIntrinsic);

private static void RunCalculateCrcTest()
{
int[] testData = { 0, 8, 215, 1024, 1024 + 15, 2034, 4096 };
for (int i = 0; i < testData.Length; i++)
{
CalculateCrcAndCompareToReference(testData[i]);
}
}
#endif
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities
/// </summary>
public static class FeatureTestRunner
{
private static readonly char[] SplitChars = new[] { ',', ' ' };
private static readonly char[] SplitChars = { ',', ' ' };

/// <summary>
/// Allows the deserialization of parameters passed to the feature test.
Expand Down Expand Up @@ -349,7 +349,7 @@ public static void RunWithHwIntrinsicsFeature<T>(

internal static Dictionary<HwIntrinsics, string> ToFeatureKeyValueCollection(this HwIntrinsics intrinsics)
{
// Loop through and translate the given values into COMPlus equivaluents
// Loop through and translate the given values into COMPlus equivalents.
var features = new Dictionary<HwIntrinsics, string>();
foreach (string intrinsic in intrinsics.ToString("G").Split(SplitChars, StringSplitOptions.RemoveEmptyEntries))
{
Expand Down Expand Up @@ -407,6 +407,12 @@ public enum HwIntrinsics
DisableBMI1 = 1 << 14,
DisableBMI2 = 1 << 15,
DisableLZCNT = 1 << 16,
AllowAll = 1 << 17
DisableArm64AdvSimd = 1 << 17,
DisableArm64Crc32 = 1 << 18,
DisableArm64Dp = 1 << 19,
DisableArm64Aes = 1 << 20,
DisableArm64Sha1 = 1 << 21,
DisableArm64Sha256 = 1 << 22,
AllowAll = 1 << 23
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@
using System.Numerics;
#if SUPPORTS_RUNTIME_INTRINSICS
using System.Runtime.Intrinsics.X86;
using Aes = System.Runtime.Intrinsics.X86.Aes;
#if NET5_0_OR_GREATER
using System.Runtime.Intrinsics.Arm;
#endif
#endif
using Xunit;
using Xunit.Abstractions;
Expand All @@ -16,11 +20,11 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.Tests
public class FeatureTestRunnerTests
{
public static TheoryData<HwIntrinsics, string[]> Intrinsics =>
new TheoryData<HwIntrinsics, string[]>
new()
{
{ HwIntrinsics.DisableAES | HwIntrinsics.AllowAll, new string[] { "EnableAES", "AllowAll" } },
{ HwIntrinsics.DisableSIMD | HwIntrinsics.DisableHWIntrinsic, new string[] { "FeatureSIMD", "EnableHWIntrinsic" } },
{ HwIntrinsics.DisableSSE42 | HwIntrinsics.DisableAVX, new string[] { "EnableSSE42", "EnableAVX" } }
{ HwIntrinsics.DisableAES | HwIntrinsics.AllowAll, new[] { "EnableAES", "AllowAll" } },
{ HwIntrinsics.DisableSIMD | HwIntrinsics.DisableHWIntrinsic, new[] { "FeatureSIMD", "EnableHWIntrinsic" } },
{ HwIntrinsics.DisableSSE42 | HwIntrinsics.DisableAVX, new[] { "EnableSSE42", "EnableAVX" } }
};

[Theory]
Expand Down Expand Up @@ -56,12 +60,9 @@ public void AllowsAllHwIntrinsicFeatures()
}

[Fact]
public void CanLimitHwIntrinsicSIMDFeatures()
{
FeatureTestRunner.RunWithHwIntrinsicsFeature(
public void CanLimitHwIntrinsicSIMDFeatures() => FeatureTestRunner.RunWithHwIntrinsicsFeature(
() => Assert.False(Vector.IsHardwareAccelerated),
HwIntrinsics.DisableSIMD);
}

#if SUPPORTS_RUNTIME_INTRINSICS
[Fact]
Expand Down Expand Up @@ -121,6 +122,14 @@ static void AssertHwIntrinsicsFeatureDisabled(string intrinsic)
Assert.False(Bmi1.IsSupported);
Assert.False(Bmi2.IsSupported);
Assert.False(Lzcnt.IsSupported);
#if NET5_0_OR_GREATER
Assert.False(AdvSimd.IsSupported);
Assert.False(System.Runtime.Intrinsics.Arm.Aes.IsSupported);
Assert.False(Crc32.IsSupported);
Assert.False(Dp.IsSupported);
Assert.False(Sha1.IsSupported);
Assert.False(Sha256.IsSupported);
#endif
break;
case HwIntrinsics.DisableSSE:
Assert.False(Sse.IsSupported);
Expand Down Expand Up @@ -167,6 +176,26 @@ static void AssertHwIntrinsicsFeatureDisabled(string intrinsic)
case HwIntrinsics.DisableLZCNT:
Assert.False(Lzcnt.IsSupported);
break;
#if NET5_0_OR_GREATER
case HwIntrinsics.DisableArm64AdvSimd:
Assert.False(AdvSimd.IsSupported);
break;
case HwIntrinsics.DisableArm64Aes:
Assert.False(System.Runtime.Intrinsics.Arm.Aes.IsSupported);
break;
case HwIntrinsics.DisableArm64Crc32:
Assert.False(Crc32.IsSupported);
break;
case HwIntrinsics.DisableArm64Dp:
Assert.False(Dp.IsSupported);
break;
case HwIntrinsics.DisableArm64Sha1:
Assert.False(Sha1.IsSupported);
break;
case HwIntrinsics.DisableArm64Sha256:
Assert.False(Sha256.IsSupported);
break;
#endif
#endif
}
}
Expand Down Expand Up @@ -226,6 +255,14 @@ static void AssertHwIntrinsicsFeatureDisabled(string serializable, string intrin
Assert.False(Bmi1.IsSupported);
Assert.False(Bmi2.IsSupported);
Assert.False(Lzcnt.IsSupported);
#if NET5_0_OR_GREATER
Assert.False(AdvSimd.IsSupported);
Assert.False(System.Runtime.Intrinsics.Arm.Aes.IsSupported);
Assert.False(Crc32.IsSupported);
Assert.False(Dp.IsSupported);
Assert.False(Sha1.IsSupported);
Assert.False(Sha256.IsSupported);
#endif
break;
case HwIntrinsics.DisableSSE:
Assert.False(Sse.IsSupported);
Expand Down Expand Up @@ -272,6 +309,26 @@ static void AssertHwIntrinsicsFeatureDisabled(string serializable, string intrin
case HwIntrinsics.DisableLZCNT:
Assert.False(Lzcnt.IsSupported);
break;
#if NET5_0_OR_GREATER
case HwIntrinsics.DisableArm64AdvSimd:
Assert.False(AdvSimd.IsSupported);
break;
case HwIntrinsics.DisableArm64Aes:
Assert.False(System.Runtime.Intrinsics.Arm.Aes.IsSupported);
break;
case HwIntrinsics.DisableArm64Crc32:
Assert.False(Crc32.IsSupported);
break;
case HwIntrinsics.DisableArm64Dp:
Assert.False(Dp.IsSupported);
break;
case HwIntrinsics.DisableArm64Sha1:
Assert.False(Sha1.IsSupported);
break;
case HwIntrinsics.DisableArm64Sha256:
Assert.False(Sha256.IsSupported);
break;
#endif
#endif
}
}
Expand Down