Skip to content

Commit 56bbc7c

Browse files
deeprobingfoidlkasperk81saucecontrol
authored
[API Implementation]: Expose general purpose Crc32 APIs (#61558)
* 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>
1 parent 23697bb commit 56bbc7c

File tree

7 files changed

+256
-28
lines changed

7 files changed

+256
-28
lines changed
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
namespace System.Numerics
5+
{
6+
internal static class Crc32ReflectedTable
7+
{
8+
internal static uint[] Generate(uint reflectedPolynomial)
9+
{
10+
uint[] table = new uint[256];
11+
12+
for (int i = 0; i < 256; i++)
13+
{
14+
uint val = (uint)i;
15+
16+
for (int j = 0; j < 8; j++)
17+
{
18+
if ((val & 0b0000_0001) == 0)
19+
{
20+
val >>= 1;
21+
}
22+
else
23+
{
24+
val = (val >> 1) ^ reflectedPolynomial;
25+
}
26+
}
27+
28+
table[i] = val;
29+
}
30+
31+
return table;
32+
}
33+
}
34+
}

src/libraries/System.IO.Hashing/src/System.IO.Hashing.csproj

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@ System.IO.Hashing.XxHash32</PackageDescription>
2020
<Compile Include="System\IO\Hashing\XxHash64.cs" />
2121
<Compile Include="System\IO\Hashing\XxHash64.State.cs" />
2222
<Compile Include="System\IO\Hashing\NonCryptographicHashAlgorithm.cs" />
23+
<Compile Include="$(CommonPath)System\Numerics\Crc32ReflectedTable.cs">
24+
<Link>Common\System\Numerics\Crc32ReflectedTable.cs</Link>
25+
</Compile>
2326
<Compile Include="System\IO\Hashing\BitOperations.cs"
2427
Condition="'$(TargetFrameworkIdentifier)' != '.NETCoreApp'" />
2528
</ItemGroup>
Lines changed: 3 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4+
using System.Numerics;
5+
46
namespace System.IO.Hashing
57
{
68
public sealed partial class Crc32 : NonCryptographicHashAlgorithm
@@ -9,32 +11,6 @@ public sealed partial class Crc32 : NonCryptographicHashAlgorithm
911
// While this implementation is based on the standard CRC-32 polynomial,
1012
// x32 + x26 + x23 + x22 + x16 + x12 + x11 + x10 + x8 + x7 + x5 + x4 + x2 + x1 + x0,
1113
// this version uses reflected bit ordering, so 0x04C11DB7 becomes 0xEDB88320
12-
private static readonly uint[] s_crcLookup = GenerateReflectedTable(0xEDB88320u);
13-
14-
private static uint[] GenerateReflectedTable(uint reflectedPolynomial)
15-
{
16-
uint[] table = new uint[256];
17-
18-
for (int i = 0; i < 256; i++)
19-
{
20-
uint val = (uint)i;
21-
22-
for (int j = 0; j < 8; j++)
23-
{
24-
if ((val & 0b0000_0001) == 0)
25-
{
26-
val >>= 1;
27-
}
28-
else
29-
{
30-
val = (val >> 1) ^ reflectedPolynomial;
31-
}
32-
}
33-
34-
table[i] = val;
35-
}
36-
37-
return table;
38-
}
14+
private static readonly uint[] s_crcLookup = Crc32ReflectedTable.Generate(0xEDB88320u);
3915
}
4016
}

src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1263,6 +1263,9 @@
12631263
<Compile Include="$(CommonPath)System\NotImplemented.cs">
12641264
<Link>Common\System\NotImplemented.cs</Link>
12651265
</Compile>
1266+
<Compile Include="$(CommonPath)System\Numerics\Crc32ReflectedTable.cs">
1267+
<Link>Common\System\Numerics\Crc32ReflectedTable.cs</Link>
1268+
</Compile>
12661269
<Compile Include="$(CommonPath)System\Obsoletions.cs">
12671270
<Link>Common\System\Obsoletions.cs</Link>
12681271
</Compile>

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

Lines changed: 161 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4+
using System.Buffers.Binary;
5+
using System.Diagnostics;
46
using System.Runtime.CompilerServices;
57
using System.Runtime.InteropServices;
68
using System.Runtime.Intrinsics;
@@ -51,7 +53,7 @@ public static class BitOperations
5153
/// <param name="value">The value.</param>
5254
[MethodImpl(MethodImplOptions.AggressiveInlining)]
5355
[CLSCompliant(false)]
54-
public static bool IsPow2(uint value) => (value & (value - 1)) == 0 && value != 0 ;
56+
public static bool IsPow2(uint value) => (value & (value - 1)) == 0 && value != 0;
5557

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

735+
/// <summary>
736+
/// Accumulates the CRC (Cyclic redundancy check) checksum.
737+
/// </summary>
738+
/// <param name="crc">The base value to calculate checksum on</param>
739+
/// <param name="data">The data for which to compute the checksum</param>
740+
/// <returns>The CRC-checksum</returns>
741+
[CLSCompliant(false)]
742+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
743+
public static uint Crc32C(uint crc, byte data)
744+
{
745+
if (Sse42.IsSupported)
746+
{
747+
return Sse42.Crc32(crc, data);
748+
}
749+
750+
if (Crc32.IsSupported)
751+
{
752+
return Crc32.ComputeCrc32C(crc, data);
753+
}
754+
755+
return Crc32Fallback.Crc32C(crc, data);
756+
}
757+
758+
/// <summary>
759+
/// Accumulates the CRC (Cyclic redundancy check) checksum.
760+
/// </summary>
761+
/// <param name="crc">The base value to calculate checksum on</param>
762+
/// <param name="data">The data for which to compute the checksum</param>
763+
/// <returns>The CRC-checksum</returns>
764+
[CLSCompliant(false)]
765+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
766+
public static uint Crc32C(uint crc, ushort data)
767+
{
768+
if (Sse42.IsSupported)
769+
{
770+
return Sse42.Crc32(crc, data);
771+
}
772+
773+
if (Crc32.IsSupported)
774+
{
775+
return Crc32.ComputeCrc32C(crc, data);
776+
}
777+
778+
return Crc32Fallback.Crc32C(crc, data);
779+
}
780+
781+
/// <summary>
782+
/// Accumulates the CRC (Cyclic redundancy check) checksum.
783+
/// </summary>
784+
/// <param name="crc">The base value to calculate checksum on</param>
785+
/// <param name="data">The data for which to compute the checksum</param>
786+
/// <returns>The CRC-checksum</returns>
787+
[CLSCompliant(false)]
788+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
789+
public static uint Crc32C(uint crc, uint data)
790+
{
791+
if (Sse42.IsSupported)
792+
{
793+
return Sse42.Crc32(crc, data);
794+
}
795+
796+
if (Crc32.IsSupported)
797+
{
798+
return Crc32.ComputeCrc32C(crc, data);
799+
}
800+
801+
return Crc32Fallback.Crc32C(crc, data);
802+
}
803+
804+
/// <summary>
805+
/// Accumulates the CRC (Cyclic redundancy check) checksum.
806+
/// </summary>
807+
/// <param name="crc">The base value to calculate checksum on</param>
808+
/// <param name="data">The data for which to compute the checksum</param>
809+
/// <returns>The CRC-checksum</returns>
810+
[CLSCompliant(false)]
811+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
812+
public static uint Crc32C(uint crc, ulong data)
813+
{
814+
if (Sse42.X64.IsSupported)
815+
{
816+
// This intrinsic returns a 64-bit register with the upper 32-bits set to 0.
817+
return (uint)Sse42.X64.Crc32(crc, data);
818+
}
819+
820+
if (Sse42.IsSupported)
821+
{
822+
uint result = Sse42.Crc32(crc, (uint)(data));
823+
return Sse42.Crc32(result, (uint)(data >> 32));
824+
}
825+
826+
if (Crc32.Arm64.IsSupported)
827+
{
828+
return Crc32.Arm64.ComputeCrc32C(crc, data);
829+
}
830+
831+
return Crc32Fallback.Crc32C(crc, data);
832+
}
833+
834+
private static class Crc32Fallback
835+
{
836+
// Pre-computed CRC-32 transition table.
837+
// While this implementation is based on the Castagnoli CRC-32 polynomial (CRC-32C),
838+
// x32 + x28 + x27 + x26 + x25 + x23 + x22 + x20 + x19 + x18 + x14 + x13 + x11 + x10 + x9 + x8 + x6 + x0,
839+
// this version uses reflected bit ordering, so 0x1EDC6F41 becomes 0x82F63B78u
840+
private static readonly uint[] s_crcTable = Crc32ReflectedTable.Generate(0x82F63B78u);
841+
842+
internal static uint Crc32C(uint crc, byte data)
843+
{
844+
ref uint lookupTable = ref MemoryMarshal.GetArrayDataReference(s_crcTable);
845+
crc = Unsafe.Add(ref lookupTable, (nint)(byte)(crc ^ data)) ^ (crc >> 8);
846+
847+
return crc;
848+
}
849+
850+
internal static uint Crc32C(uint crc, ushort data)
851+
{
852+
ref uint lookupTable = ref MemoryMarshal.GetArrayDataReference(s_crcTable);
853+
854+
crc = Unsafe.Add(ref lookupTable, (nint)(byte)(crc ^ (byte)data)) ^ (crc >> 8);
855+
data >>= 8;
856+
crc = Unsafe.Add(ref lookupTable, (nint)(byte)(crc ^ data)) ^ (crc >> 8);
857+
858+
return crc;
859+
}
860+
861+
internal static uint Crc32C(uint crc, uint data)
862+
{
863+
ref uint lookupTable = ref MemoryMarshal.GetArrayDataReference(s_crcTable);
864+
return Crc32CCore(ref lookupTable, crc, data);
865+
}
866+
867+
internal static uint Crc32C(uint crc, ulong data)
868+
{
869+
ref uint lookupTable = ref MemoryMarshal.GetArrayDataReference(s_crcTable);
870+
871+
crc = Crc32CCore(ref lookupTable, crc, (uint)data);
872+
data >>= 32;
873+
crc = Crc32CCore(ref lookupTable, crc, (uint)data);
874+
875+
return crc;
876+
}
877+
878+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
879+
private static uint Crc32CCore(ref uint lookupTable, uint crc, uint data)
880+
{
881+
crc = Unsafe.Add(ref lookupTable, (nint)(byte)(crc ^ (byte)data)) ^ (crc >> 8);
882+
data >>= 8;
883+
crc = Unsafe.Add(ref lookupTable, (nint)(byte)(crc ^ (byte)data)) ^ (crc >> 8);
884+
data >>= 8;
885+
crc = Unsafe.Add(ref lookupTable, (nint)(byte)(crc ^ (byte)data)) ^ (crc >> 8);
886+
data >>= 8;
887+
crc = Unsafe.Add(ref lookupTable, (nint)(byte)(crc ^ data)) ^ (crc >> 8);
888+
889+
return crc;
890+
}
891+
}
892+
733893
/// <summary>
734894
/// Reset the lowest significant bit in the given value
735895
/// </summary>

src/libraries/System.Runtime.Extensions/tests/System/Numerics/BitOperationsTests.cs

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -887,5 +887,49 @@ public static void BitOps_RoundUpToPow2_nuint_64(ulong value, ulong expected)
887887
{
888888
Assert.Equal(expected, BitOperations.RoundUpToPowerOf2((nuint) value));
889889
}
890+
891+
[Theory]
892+
[InlineData(0, 0, 0)]
893+
[InlineData(0, 120, 4215344322)]
894+
[InlineData(0, byte.MaxValue, 2910671697)]
895+
[InlineData(123, byte.MaxValue, 1164749927)]
896+
public static void BitOps_Crc32C_byte(uint crc, byte data, uint expected)
897+
{
898+
uint obtained = BitOperations.Crc32C(crc, data);
899+
Assert.Equal(expected, obtained);
900+
}
901+
902+
[Theory]
903+
[InlineData(0, 0, 0)]
904+
[InlineData(0, 120, 575477567)]
905+
[InlineData(0, ushort.MaxValue, 245266386)]
906+
[InlineData(123, ushort.MaxValue, 406112372)]
907+
public static void BitOps_Crc32C_ushort(uint crc, ushort data, uint expected)
908+
{
909+
uint obtained = BitOperations.Crc32C(crc, data);
910+
Assert.Equal(expected, obtained);
911+
}
912+
913+
[Theory]
914+
[InlineData(0, 0, 0)]
915+
[InlineData(0, 120, 1671666103)]
916+
[InlineData(0, uint.MaxValue, 3080238136)]
917+
[InlineData(123, uint.MaxValue, 3055133878)]
918+
public static void BitOps_Crc32C_uint(uint crc, uint data, uint expected)
919+
{
920+
uint obtained = BitOperations.Crc32C(crc, data);
921+
Assert.Equal(expected, obtained);
922+
}
923+
924+
[Theory]
925+
[InlineData(0, 0, 0)]
926+
[InlineData(0, 120, 3511526341)]
927+
[InlineData(0, ulong.MaxValue, 3293575501)]
928+
[InlineData(123, ulong.MaxValue, 3460750817)]
929+
public static void BitOps_Crc32C_ulong(uint crc, ulong data, uint expected)
930+
{
931+
uint obtained = BitOperations.Crc32C(crc, data);
932+
Assert.Equal(expected, obtained);
933+
}
890934
}
891935
}

src/libraries/System.Runtime/ref/System.Runtime.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10217,6 +10217,14 @@ public static partial class BitOperations
1021710217
public static int TrailingZeroCount(ulong value) { throw null; }
1021810218
[System.CLSCompliantAttribute(false)]
1021910219
public static int TrailingZeroCount(nuint value) { throw null; }
10220+
[System.CLSCompliantAttribute(false)]
10221+
public static uint Crc32C(uint crc, byte data) { throw null; }
10222+
[System.CLSCompliantAttribute(false)]
10223+
public static uint Crc32C(uint crc, ushort data) { throw null; }
10224+
[System.CLSCompliantAttribute(false)]
10225+
public static uint Crc32C(uint crc, uint data) { throw null; }
10226+
[System.CLSCompliantAttribute(false)]
10227+
public static uint Crc32C(uint crc, ulong data) { throw null; }
1022010228
}
1022110229
public partial interface IAdditionOperators<TSelf, TOther, TResult> where TSelf : System.Numerics.IAdditionOperators<TSelf, TOther, TResult>?
1022210230
{

0 commit comments

Comments
 (0)