From 7d1ad32e02a0b60f89026250b2d1707bc1cf8a6b Mon Sep 17 00:00:00 2001 From: EgorBo Date: Fri, 24 Apr 2026 14:58:23 +0200 Subject: [PATCH 1/2] Use safe Span.Slice loop pattern in CRC32 ARM scalar paths Rewrites the four ARM/ARM64 scalar CRC update paths in `Crc32ParameterSet.WellKnown` (UpdateScalarArm, UpdateScalarArm64 for both Crc32 and Crc32C) to use the safe `while (source.Length >= sizeof(T)) { ...; source = source.Slice(sizeof(T)); }` pattern, replacing `Unsafe.ReadUnaligned` / `Unsafe.Add(ref, off)` with `BinaryPrimitives.ReadUInt64LittleEndian` / `ReadUInt32LittleEndian`. The JIT recognizes this exact loop shape and elides the bounds checks. ARM CRC32 instructions are byte-stream and .NET only runs ARM in little-endian mode, so the explicit `LittleEndian` reads are equivalent to the previous host-endian `Unsafe.ReadUnaligned`. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../IO/Hashing/Crc32ParameterSet.WellKnown.cs | 59 ++++++------------- 1 file changed, 18 insertions(+), 41 deletions(-) diff --git a/src/libraries/System.IO.Hashing/src/System/IO/Hashing/Crc32ParameterSet.WellKnown.cs b/src/libraries/System.IO.Hashing/src/System/IO/Hashing/Crc32ParameterSet.WellKnown.cs index b48bffa843d4c4..fef0672ff20a43 100644 --- a/src/libraries/System.IO.Hashing/src/System/IO/Hashing/Crc32ParameterSet.WellKnown.cs +++ b/src/libraries/System.IO.Hashing/src/System/IO/Hashing/Crc32ParameterSet.WellKnown.cs @@ -1,8 +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; namespace System.IO.Hashing @@ -133,20 +133,12 @@ private static uint UpdateScalarArm64(uint crc, ReadOnlySpan source) Debug.Assert(System.Runtime.Intrinsics.Arm.Crc32.Arm64.IsSupported, "ARM CRC support is required."); // Compute in 8 byte chunks - if (source.Length >= sizeof(ulong)) + while (source.Length >= sizeof(ulong)) { - ref byte ptr = ref MemoryMarshal.GetReference(source); - - // Exclude trailing bytes not a multiple of 8 - int longLength = source.Length & ~0x7; - - for (int i = 0; i < longLength; i += sizeof(ulong)) - { - crc = System.Runtime.Intrinsics.Arm.Crc32.Arm64.ComputeCrc32( - crc, - Unsafe.ReadUnaligned(ref Unsafe.Add(ref ptr, i))); - } - source = source.Slice(longLength); + crc = System.Runtime.Intrinsics.Arm.Crc32.Arm64.ComputeCrc32( + crc, + BinaryPrimitives.ReadUInt64LittleEndian(source)); + source = source.Slice(sizeof(ulong)); } // Compute remaining bytes @@ -163,21 +155,12 @@ private static uint UpdateScalarArm(uint crc, ReadOnlySpan source) Debug.Assert(System.Runtime.Intrinsics.Arm.Crc32.IsSupported, "ARM CRC support is required."); // Compute in 4 byte chunks - if (source.Length >= sizeof(uint)) + while (source.Length >= sizeof(uint)) { - ref byte ptr = ref MemoryMarshal.GetReference(source); - - // Exclude trailing bytes not a multiple of 4 - int intLength = source.Length & ~0x3; - - for (int i = 0; i < intLength; i += sizeof(uint)) - { - crc = System.Runtime.Intrinsics.Arm.Crc32.ComputeCrc32( - crc, - Unsafe.ReadUnaligned(ref Unsafe.Add(ref ptr, i))); - } - - source = source.Slice(intLength); + crc = System.Runtime.Intrinsics.Arm.Crc32.ComputeCrc32( + crc, + BinaryPrimitives.ReadUInt32LittleEndian(source)); + source = source.Slice(sizeof(uint)); } // Compute remaining bytes @@ -243,33 +226,27 @@ private static uint UpdateIntrinsic(uint crc, ReadOnlySpan source) else { Debug.Assert(System.Runtime.Intrinsics.Arm.Crc32.IsSupported); - ref byte ptr = ref MemoryMarshal.GetReference(source); - int offset = 0; if (System.Runtime.Intrinsics.Arm.Crc32.Arm64.IsSupported) { - int longLength = source.Length & ~0x7; // Exclude trailing bytes not a multiple of 8 - - for (; offset < longLength; offset += sizeof(ulong)) + while (source.Length >= sizeof(ulong)) { crc = System.Runtime.Intrinsics.Arm.Crc32.Arm64.ComputeCrc32C( crc, - Unsafe.ReadUnaligned(ref Unsafe.Add(ref ptr, offset))); + BinaryPrimitives.ReadUInt64LittleEndian(source)); + source = source.Slice(sizeof(ulong)); } } - int intLength = source.Length & ~0x3; // Exclude trailing bytes not a multiple of 4 - - for (; offset < intLength; offset += sizeof(uint)) + while (source.Length >= sizeof(uint)) { crc = System.Runtime.Intrinsics.Arm.Crc32.ComputeCrc32C( crc, - Unsafe.ReadUnaligned(ref Unsafe.Add(ref ptr, offset))); + BinaryPrimitives.ReadUInt32LittleEndian(source)); + source = source.Slice(sizeof(uint)); } - ReadOnlySpan remainingBytes = source.Slice(offset); - - foreach (byte value in remainingBytes) + foreach (byte value in source) { crc = System.Runtime.Intrinsics.Arm.Crc32.ComputeCrc32C(crc, value); } From 32a1c70e8d2f7fb7c8a892a2b8967ce9013f0b58 Mon Sep 17 00:00:00 2001 From: Egor Bogatov Date: Fri, 24 Apr 2026 15:04:29 +0200 Subject: [PATCH 2/2] Update src/libraries/System.IO.Hashing/src/System/IO/Hashing/Crc32ParameterSet.WellKnown.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../src/System/IO/Hashing/Crc32ParameterSet.WellKnown.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/libraries/System.IO.Hashing/src/System/IO/Hashing/Crc32ParameterSet.WellKnown.cs b/src/libraries/System.IO.Hashing/src/System/IO/Hashing/Crc32ParameterSet.WellKnown.cs index fef0672ff20a43..32623d12e21cad 100644 --- a/src/libraries/System.IO.Hashing/src/System/IO/Hashing/Crc32ParameterSet.WellKnown.cs +++ b/src/libraries/System.IO.Hashing/src/System/IO/Hashing/Crc32ParameterSet.WellKnown.cs @@ -1,7 +1,9 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +#if NET using System.Buffers.Binary; +#endif using System.Diagnostics; using System.Runtime.InteropServices;