Skip to content

Commit

Permalink
[API Implementation]: Expose general purpose Crc32 APIs (#61558)
Browse files Browse the repository at this point in the history
* Expose Crc32 Implementation and implement Crc32 software fallback

* Fix signature of BitOperation Crc32 methods

* Implement common test cases

* Add X64 Intrinsic support for Crc32(uint crc, ulong data)

* Add documentation comments

* Remove Hwintrinsic doc-comment

* Rename Crc32 to Crc32C

* Remove unnessecary heap allocation

* Fix software fallback using table-based-CRC

* Usage of uint32_t __crc32ch (uint32_t a, uint32_t b)

* Remove Crc.Sse42 implementation for Crc32C(uint crc, ulong data)

There is no implementation which returns `uint32_t`. See https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#techs=MMX,SSE,SSE2,SSE3,SSSE3,SSE4_1,SSE4_2,AVX,AVX2,FMA,AVX_VNNI,AVX_512,KNC,AMX,SVML,Other&text=crc

* Add SSE 4.2 x64 implementation with byte truncation for Crc32C(uint crc, ulong data)

* Replace Unsafe.As with unchecked-uint cast

* Usage of WriteUnaligned instead of As

Co-authored-by: Günther Foidl <gue@korporal.at>

* Usage of WriteUnaligned instead of As

Co-authored-by: Günther Foidl <gue@korporal.at>

* Usage of WriteUnaligned instead of As

Co-authored-by: Günther Foidl <gue@korporal.at>

* Usage of explicit types

* Fix Software-Fallback Table to Castalogni equivalent

* Fix test signature

* Remove mask for software fallback

* reuse reflected table generator

* Fix test data

* Usage of CRC Table Generator instead of constant values

* Fix solution file

* Revert "Usage of CRC Table Generator instead of constant values"

This reverts commit d44a215.

* Fix solution file

* Make Crc32ReflectedTable static

Co-authored-by: kasperk81 <83082615+kasperk81@users.noreply.github.com>

* Fix wrong intrinsic for Arm64/BitOperations.Crc32C(uint crc, ulong data)

* Reduce amount of stackalloc statements and replace them with MemoryMarshal.CreateReadOnlySpan

* Loop unwinding and performance optimization

* Remove unnessecary cast

Co-authored-by: Clinton Ingram <clinton.ingram@outlook.com>

* Use MemoryMarshal.GetArrayDataReference instead of direct array access

* Remove unnessecary newlines

* Update src/libraries/System.Private.CoreLib/src/System/Numerics/BitOperations.cs

Co-authored-by: kasperk81 <83082615+kasperk81@users.noreply.github.com>

* Add x64 SSE check

Co-authored-by: kasperk81 <83082615+kasperk81@users.noreply.github.com>

* Move Crc32 Software into own class

* Remove IsSupported check

* Fix parameter naming

* Fix fallback implementation method naming

* Improve documentation

* Move bswap into Crc32Fallback class

* Implement efficient usage of SSE x86/x64

Co-authored-by: Clinton Ingram <clinton.ingram@outlook.com>

* Style Changes for BitOperations.cs

* Remove unnessecary `bswap` of a single byte

* Fix doc comments

* fix doc

* Fix

* Merge remote-tracking branch 'upstream/main' into issue-2036

* Apply suggestions

Co-authored-by: Günther Foidl <gue@korporal.at>
Co-authored-by: kasperk81 <83082615+kasperk81@users.noreply.github.com>
Co-authored-by: Clinton Ingram <clinton.ingram@outlook.com>
  • Loading branch information
4 people authored Oct 3, 2022
1 parent 23697bb commit 56bbc7c
Show file tree
Hide file tree
Showing 7 changed files with 256 additions and 28 deletions.
34 changes: 34 additions & 0 deletions src/libraries/Common/src/System/Numerics/Crc32ReflectedTable.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

namespace System.Numerics
{
internal static class Crc32ReflectedTable
{
internal static uint[] Generate(uint reflectedPolynomial)
{
uint[] table = new uint[256];

for (int i = 0; i < 256; i++)
{
uint val = (uint)i;

for (int j = 0; j < 8; j++)
{
if ((val & 0b0000_0001) == 0)
{
val >>= 1;
}
else
{
val = (val >> 1) ^ reflectedPolynomial;
}
}

table[i] = val;
}

return table;
}
}
}
3 changes: 3 additions & 0 deletions src/libraries/System.IO.Hashing/src/System.IO.Hashing.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ System.IO.Hashing.XxHash32</PackageDescription>
<Compile Include="System\IO\Hashing\XxHash64.cs" />
<Compile Include="System\IO\Hashing\XxHash64.State.cs" />
<Compile Include="System\IO\Hashing\NonCryptographicHashAlgorithm.cs" />
<Compile Include="$(CommonPath)System\Numerics\Crc32ReflectedTable.cs">
<Link>Common\System\Numerics\Crc32ReflectedTable.cs</Link>
</Compile>
<Compile Include="System\IO\Hashing\BitOperations.cs"
Condition="'$(TargetFrameworkIdentifier)' != '.NETCoreApp'" />
</ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Numerics;

namespace System.IO.Hashing
{
public sealed partial class Crc32 : NonCryptographicHashAlgorithm
Expand All @@ -9,32 +11,6 @@ public sealed partial class Crc32 : NonCryptographicHashAlgorithm
// While this implementation is based on the standard CRC-32 polynomial,
// x32 + x26 + x23 + x22 + x16 + x12 + x11 + x10 + x8 + x7 + x5 + x4 + x2 + x1 + x0,
// this version uses reflected bit ordering, so 0x04C11DB7 becomes 0xEDB88320
private static readonly uint[] s_crcLookup = GenerateReflectedTable(0xEDB88320u);

private static uint[] GenerateReflectedTable(uint reflectedPolynomial)
{
uint[] table = new uint[256];

for (int i = 0; i < 256; i++)
{
uint val = (uint)i;

for (int j = 0; j < 8; j++)
{
if ((val & 0b0000_0001) == 0)
{
val >>= 1;
}
else
{
val = (val >> 1) ^ reflectedPolynomial;
}
}

table[i] = val;
}

return table;
}
private static readonly uint[] s_crcLookup = Crc32ReflectedTable.Generate(0xEDB88320u);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -1263,6 +1263,9 @@
<Compile Include="$(CommonPath)System\NotImplemented.cs">
<Link>Common\System\NotImplemented.cs</Link>
</Compile>
<Compile Include="$(CommonPath)System\Numerics\Crc32ReflectedTable.cs">
<Link>Common\System\Numerics\Crc32ReflectedTable.cs</Link>
</Compile>
<Compile Include="$(CommonPath)System\Obsoletions.cs">
<Link>Common\System\Obsoletions.cs</Link>
</Compile>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Buffers.Binary;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Intrinsics;
Expand Down Expand Up @@ -51,7 +53,7 @@ public static class BitOperations
/// <param name="value">The value.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[CLSCompliant(false)]
public static bool IsPow2(uint value) => (value & (value - 1)) == 0 && value != 0 ;
public static bool IsPow2(uint value) => (value & (value - 1)) == 0 && value != 0;

/// <summary>
/// Evaluate whether a given integral value is a power of 2.
Expand Down Expand Up @@ -730,6 +732,164 @@ public static nuint RotateRight(nuint value, int offset)
#endif
}

/// <summary>
/// Accumulates the CRC (Cyclic redundancy check) checksum.
/// </summary>
/// <param name="crc">The base value to calculate checksum on</param>
/// <param name="data">The data for which to compute the checksum</param>
/// <returns>The CRC-checksum</returns>
[CLSCompliant(false)]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static uint Crc32C(uint crc, byte data)
{
if (Sse42.IsSupported)
{
return Sse42.Crc32(crc, data);
}

if (Crc32.IsSupported)
{
return Crc32.ComputeCrc32C(crc, data);
}

return Crc32Fallback.Crc32C(crc, data);
}

/// <summary>
/// Accumulates the CRC (Cyclic redundancy check) checksum.
/// </summary>
/// <param name="crc">The base value to calculate checksum on</param>
/// <param name="data">The data for which to compute the checksum</param>
/// <returns>The CRC-checksum</returns>
[CLSCompliant(false)]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static uint Crc32C(uint crc, ushort data)
{
if (Sse42.IsSupported)
{
return Sse42.Crc32(crc, data);
}

if (Crc32.IsSupported)
{
return Crc32.ComputeCrc32C(crc, data);
}

return Crc32Fallback.Crc32C(crc, data);
}

/// <summary>
/// Accumulates the CRC (Cyclic redundancy check) checksum.
/// </summary>
/// <param name="crc">The base value to calculate checksum on</param>
/// <param name="data">The data for which to compute the checksum</param>
/// <returns>The CRC-checksum</returns>
[CLSCompliant(false)]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static uint Crc32C(uint crc, uint data)
{
if (Sse42.IsSupported)
{
return Sse42.Crc32(crc, data);
}

if (Crc32.IsSupported)
{
return Crc32.ComputeCrc32C(crc, data);
}

return Crc32Fallback.Crc32C(crc, data);
}

/// <summary>
/// Accumulates the CRC (Cyclic redundancy check) checksum.
/// </summary>
/// <param name="crc">The base value to calculate checksum on</param>
/// <param name="data">The data for which to compute the checksum</param>
/// <returns>The CRC-checksum</returns>
[CLSCompliant(false)]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static uint Crc32C(uint crc, ulong data)
{
if (Sse42.X64.IsSupported)
{
// This intrinsic returns a 64-bit register with the upper 32-bits set to 0.
return (uint)Sse42.X64.Crc32(crc, data);
}

if (Sse42.IsSupported)
{
uint result = Sse42.Crc32(crc, (uint)(data));
return Sse42.Crc32(result, (uint)(data >> 32));
}

if (Crc32.Arm64.IsSupported)
{
return Crc32.Arm64.ComputeCrc32C(crc, data);
}

return Crc32Fallback.Crc32C(crc, data);
}

private static class Crc32Fallback
{
// Pre-computed CRC-32 transition table.
// While this implementation is based on the Castagnoli CRC-32 polynomial (CRC-32C),
// x32 + x28 + x27 + x26 + x25 + x23 + x22 + x20 + x19 + x18 + x14 + x13 + x11 + x10 + x9 + x8 + x6 + x0,
// this version uses reflected bit ordering, so 0x1EDC6F41 becomes 0x82F63B78u
private static readonly uint[] s_crcTable = Crc32ReflectedTable.Generate(0x82F63B78u);

internal static uint Crc32C(uint crc, byte data)
{
ref uint lookupTable = ref MemoryMarshal.GetArrayDataReference(s_crcTable);
crc = Unsafe.Add(ref lookupTable, (nint)(byte)(crc ^ data)) ^ (crc >> 8);

return crc;
}

internal static uint Crc32C(uint crc, ushort data)
{
ref uint lookupTable = ref MemoryMarshal.GetArrayDataReference(s_crcTable);

crc = Unsafe.Add(ref lookupTable, (nint)(byte)(crc ^ (byte)data)) ^ (crc >> 8);
data >>= 8;
crc = Unsafe.Add(ref lookupTable, (nint)(byte)(crc ^ data)) ^ (crc >> 8);

return crc;
}

internal static uint Crc32C(uint crc, uint data)
{
ref uint lookupTable = ref MemoryMarshal.GetArrayDataReference(s_crcTable);
return Crc32CCore(ref lookupTable, crc, data);
}

internal static uint Crc32C(uint crc, ulong data)
{
ref uint lookupTable = ref MemoryMarshal.GetArrayDataReference(s_crcTable);

crc = Crc32CCore(ref lookupTable, crc, (uint)data);
data >>= 32;
crc = Crc32CCore(ref lookupTable, crc, (uint)data);

return crc;
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static uint Crc32CCore(ref uint lookupTable, uint crc, uint data)
{
crc = Unsafe.Add(ref lookupTable, (nint)(byte)(crc ^ (byte)data)) ^ (crc >> 8);
data >>= 8;
crc = Unsafe.Add(ref lookupTable, (nint)(byte)(crc ^ (byte)data)) ^ (crc >> 8);
data >>= 8;
crc = Unsafe.Add(ref lookupTable, (nint)(byte)(crc ^ (byte)data)) ^ (crc >> 8);
data >>= 8;
crc = Unsafe.Add(ref lookupTable, (nint)(byte)(crc ^ data)) ^ (crc >> 8);

return crc;
}
}

/// <summary>
/// Reset the lowest significant bit in the given value
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -887,5 +887,49 @@ public static void BitOps_RoundUpToPow2_nuint_64(ulong value, ulong expected)
{
Assert.Equal(expected, BitOperations.RoundUpToPowerOf2((nuint) value));
}

[Theory]
[InlineData(0, 0, 0)]
[InlineData(0, 120, 4215344322)]
[InlineData(0, byte.MaxValue, 2910671697)]
[InlineData(123, byte.MaxValue, 1164749927)]
public static void BitOps_Crc32C_byte(uint crc, byte data, uint expected)
{
uint obtained = BitOperations.Crc32C(crc, data);
Assert.Equal(expected, obtained);
}

[Theory]
[InlineData(0, 0, 0)]
[InlineData(0, 120, 575477567)]
[InlineData(0, ushort.MaxValue, 245266386)]
[InlineData(123, ushort.MaxValue, 406112372)]
public static void BitOps_Crc32C_ushort(uint crc, ushort data, uint expected)
{
uint obtained = BitOperations.Crc32C(crc, data);
Assert.Equal(expected, obtained);
}

[Theory]
[InlineData(0, 0, 0)]
[InlineData(0, 120, 1671666103)]
[InlineData(0, uint.MaxValue, 3080238136)]
[InlineData(123, uint.MaxValue, 3055133878)]
public static void BitOps_Crc32C_uint(uint crc, uint data, uint expected)
{
uint obtained = BitOperations.Crc32C(crc, data);
Assert.Equal(expected, obtained);
}

[Theory]
[InlineData(0, 0, 0)]
[InlineData(0, 120, 3511526341)]
[InlineData(0, ulong.MaxValue, 3293575501)]
[InlineData(123, ulong.MaxValue, 3460750817)]
public static void BitOps_Crc32C_ulong(uint crc, ulong data, uint expected)
{
uint obtained = BitOperations.Crc32C(crc, data);
Assert.Equal(expected, obtained);
}
}
}
8 changes: 8 additions & 0 deletions src/libraries/System.Runtime/ref/System.Runtime.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10217,6 +10217,14 @@ public static partial class BitOperations
public static int TrailingZeroCount(ulong value) { throw null; }
[System.CLSCompliantAttribute(false)]
public static int TrailingZeroCount(nuint value) { throw null; }
[System.CLSCompliantAttribute(false)]
public static uint Crc32C(uint crc, byte data) { throw null; }
[System.CLSCompliantAttribute(false)]
public static uint Crc32C(uint crc, ushort data) { throw null; }
[System.CLSCompliantAttribute(false)]
public static uint Crc32C(uint crc, uint data) { throw null; }
[System.CLSCompliantAttribute(false)]
public static uint Crc32C(uint crc, ulong data) { throw null; }
}
public partial interface IAdditionOperators<TSelf, TOther, TResult> where TSelf : System.Numerics.IAdditionOperators<TSelf, TOther, TResult>?
{
Expand Down

0 comments on commit 56bbc7c

Please sign in to comment.