From e29deddd67ff344399947afbf532490c1cf14f3e Mon Sep 17 00:00:00 2001 From: kzrnm Date: Thu, 3 Jul 2025 02:01:35 +0900 Subject: [PATCH 01/10] Add test caces for BitArray.*Shift --- .../tests/BitArray/BitArray_OperatorsTests.cs | 24 +++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/src/libraries/System.Collections/tests/BitArray/BitArray_OperatorsTests.cs b/src/libraries/System.Collections/tests/BitArray/BitArray_OperatorsTests.cs index cf4ae6c477a68c..9d8e91f46a8c1f 100644 --- a/src/libraries/System.Collections/tests/BitArray/BitArray_OperatorsTests.cs +++ b/src/libraries/System.Collections/tests/BitArray/BitArray_OperatorsTests.cs @@ -245,13 +245,33 @@ public static void Xor_With_Resize(BitArray left, BitArray right, int newLeftLen public static IEnumerable Shift_Data() { - foreach (int size in new[] { 0, 1, BitsPerInt32 / 2, BitsPerInt32, BitsPerInt32 + 1, 2 * BitsPerInt32, 2 * BitsPerInt32 + 1 }) + Random random = new Random(0); + foreach (int size in new[] { + 0, + 1, + BitsPerInt32 / 2, + BitsPerInt32, + BitsPerInt32 + 1, + 2 * BitsPerInt32 - 1, + 2 * BitsPerInt32 + 1, + 1023, + 1024, + 1025, + }) { - foreach (int shift in new[] { 0, 1, size / 2, size - 1, size }.Where(s => s >= 0).Distinct()) + foreach (int shift in new[] { + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, + 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, + size / 3, size / 2, size / 2 + 1, size - 1, size, + }.Where(s => s >= 0).Distinct()) { yield return new object[] { size, new int[] { /* deliberately empty */ }, shift }; yield return new object[] { size, Enumerable.Range(0, size), shift }; + int[] nums = Enumerable.Range(0, size).ToArray(); + random.Shuffle(nums); + yield return new object[] { size, nums.Take(size / 2), shift }; + if (size > 1) { foreach (int position in new[] { 0, size / 2, size - 1 }) From a01656a44f8179be46264acd9b57ebc2403d6fd9 Mon Sep 17 00:00:00 2001 From: kzrnm Date: Thu, 3 Jul 2025 02:20:05 +0900 Subject: [PATCH 02/10] hardware intrinsic in BitArray.*Shift --- .../src/System/Collections/BitArray.cs | 171 +++++++++++++----- 1 file changed, 129 insertions(+), 42 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/Collections/BitArray.cs b/src/libraries/System.Private.CoreLib/src/System/Collections/BitArray.cs index 66fe6f6c37eff6..e79a3ef2727d95 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Collections/BitArray.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Collections/BitArray.cs @@ -513,51 +513,90 @@ public BitArray RightShift(int count) return this; } - Span intSpan = MemoryMarshal.Cast((Span)_array); - + Span thisSpan = new Span(_array, 0, GetByteArrayLengthFromBitLength(_bitLength)); int toIndex = 0; - int ints = GetInt32ArrayLengthFromBitLength(_bitLength); + if (count < _bitLength) { - // We can not use Math.DivRem without taking a dependency on System.Runtime.Extensions - (int fromIndex, int shiftCount) = Math.DivRem(count, 32); - int extraBits = (int)((uint)_bitLength % 32); + (int fromIndex, int shiftCount) = Math.DivRem(count, BitsPerByte); if (shiftCount == 0) { - // Cannot use `(1u << extraBits) - 1u` as the mask - // because for extraBits == 0, we need the mask to be 111...111, not 0. - // In that case, we are shifting a uint by 32, which could be considered undefined. - // The result of a shift operation is undefined ... if the right operand - // is greater than or equal to the width in bits of the promoted left operand, - // https://learn.microsoft.com/cpp/c-language/bitwise-shift-operators?view=vs-2017 - // However, the compiler protects us from undefined behaviour by constraining the - // right operand to between 0 and width - 1 (inclusive), i.e. right_operand = (right_operand % width). - uint mask = uint.MaxValue >> (BitsPerInt32 - extraBits); - intSpan[ints - 1] &= ReverseIfBE((int)mask); - - intSpan.Slice((int)fromIndex, ints - fromIndex).CopyTo(intSpan); - toIndex = ints - fromIndex; + thisSpan.Slice(fromIndex).CopyTo(thisSpan); + toIndex = thisSpan.Length - fromIndex; } else { - int lastIndex = ints - 1; + if (Vector512.IsHardwareAccelerated) + { + toIndex = Apply>(shiftCount, fromIndex, thisSpan); + } + else if (Vector256.IsHardwareAccelerated) + { + toIndex = Apply>(shiftCount, fromIndex, thisSpan); + } + else if (Vector128.IsHardwareAccelerated) + { + toIndex = Apply>(shiftCount, fromIndex, thisSpan); + } + fromIndex += toIndex; + + ref byte p = ref MemoryMarshal.GetReference(thisSpan); - while (fromIndex < lastIndex) + int carry32Count = BitsPerInt32 - shiftCount; + while (fromIndex < thisSpan.Length - 4) { - uint right = (uint)ReverseIfBE(intSpan[fromIndex]) >> shiftCount; - int left = ReverseIfBE(intSpan[++fromIndex]) << (BitsPerInt32 - shiftCount); - intSpan[toIndex++] = ReverseIfBE(left | (int)right); + int lo = ReverseIfBE(Unsafe.ReadUnaligned(ref Unsafe.AddByteOffset(ref p, (uint)fromIndex))) >>> shiftCount; + int hi = Unsafe.AddByteOffset(ref p, (uint)(fromIndex + 4)) << carry32Count; + int result = ReverseIfBE(hi | lo); + Unsafe.WriteUnaligned(ref Unsafe.AddByteOffset(ref p, toIndex), result); + + fromIndex += 4; + toIndex += 4; } - uint mask = uint.MaxValue >> (BitsPerInt32 - extraBits); - mask &= (uint)ReverseIfBE(intSpan[fromIndex]); - intSpan[toIndex++] = ReverseIfBE((int)(mask >> shiftCount)); + int carryCount = BitsPerByte - shiftCount; + while (fromIndex < thisSpan.Length) + { + int lo = thisSpan[fromIndex] >>> shiftCount; + int hi = + fromIndex + 1 < thisSpan.Length + ? thisSpan[fromIndex + 1] << carryCount + : 0; + + thisSpan[toIndex] = (byte)(hi | lo); + + fromIndex++; + toIndex++; + } } } - intSpan.Slice(toIndex, ints - toIndex).Clear(); + thisSpan.Slice(toIndex).Clear(); _version++; return this; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + static int Apply(int shiftCount, int fromIndex, Span thisSpan) + where TVector : ISimdVector + { + ref byte p = ref MemoryMarshal.GetReference(thisSpan); + int carryCount = BitsPerByte - shiftCount; + + int toIndex = 0; + + while (fromIndex <= thisSpan.Length - (TVector.ElementCount + 1)) + { + TVector lo = TVector.LoadUnsafe(ref p, (uint)fromIndex) >>> shiftCount; + TVector hi = TVector.LoadUnsafe(ref p, (uint)(fromIndex + 1)) << carryCount; + TVector result = lo | hi; + result.StoreUnsafe(ref p, (uint)toIndex); + + fromIndex += TVector.ElementCount; + toIndex += TVector.ElementCount; + } + + return toIndex; + } } /// @@ -576,41 +615,89 @@ public BitArray LeftShift(int count) return this; } - Span intSpan = MemoryMarshal.Cast((Span)_array); + Span thisSpan = new Span(_array, 0, GetByteArrayLengthFromBitLength(_bitLength)); int lengthToClear; if (count < _bitLength) { - int lastIndex = (int)((uint)(_bitLength - 1) / BitsPerInt32); - - (lengthToClear, int shiftCount) = Math.DivRem(count, BitsPerInt32); + (lengthToClear, int shiftCount) = Math.DivRem(count, BitsPerByte); if (shiftCount == 0) { - intSpan.Slice(0, lastIndex + 1 - lengthToClear).CopyTo(intSpan.Slice(lengthToClear)); + thisSpan.Slice(0, thisSpan.Length - lengthToClear).CopyTo(thisSpan.Slice(lengthToClear)); } else { - int fromindex = lastIndex - lengthToClear; + int toIndex = thisSpan.Length; + int fromIndex = toIndex - lengthToClear; + + if (Vector512.IsHardwareAccelerated) + { + toIndex = Apply>(shiftCount, fromIndex, thisSpan); + } + else if (Vector256.IsHardwareAccelerated) + { + toIndex = Apply>(shiftCount, fromIndex, thisSpan); + } + else if (Vector128.IsHardwareAccelerated) + { + toIndex = Apply>(shiftCount, fromIndex, thisSpan); + } + fromIndex = toIndex - lengthToClear; + + ref byte p = ref MemoryMarshal.GetReference(thisSpan); + + int carryCount = BitsPerByte - shiftCount; + while (fromIndex >= 5) + { + int hi = ReverseIfBE(Unsafe.ReadUnaligned(ref Unsafe.AddByteOffset(ref p, (uint)(fromIndex -= 4)))) << shiftCount; + int lo = Unsafe.AddByteOffset(ref p, (uint)(fromIndex - 1)) >>> carryCount; + int result = ReverseIfBE(hi | lo); + Unsafe.WriteUnaligned(ref Unsafe.AddByteOffset(ref p, toIndex -= 4), result); + } - while (fromindex > 0) + while (--fromIndex >= 0) { - int left = ReverseIfBE(intSpan[fromindex]) << shiftCount; - uint right = (uint)ReverseIfBE(intSpan[--fromindex]) >> (BitsPerInt32 - shiftCount); - intSpan[lastIndex] = ReverseIfBE(left | (int)right); - lastIndex--; + int hi = thisSpan[fromIndex] << shiftCount; + int lo = + fromIndex > 0 + ? thisSpan[fromIndex - 1] >>> carryCount + : 0; + + thisSpan[--toIndex] = (byte)(hi | lo); } - intSpan[lastIndex] = ReverseIfBE(ReverseIfBE(intSpan[fromindex]) << shiftCount); + + Debug.Assert(toIndex == lengthToClear); } } else { - lengthToClear = GetInt32ArrayLengthFromBitLength(_bitLength); // Clear all + lengthToClear = thisSpan.Length; // Clear all } - intSpan.Slice(0, lengthToClear).Clear(); + thisSpan.Slice(0, lengthToClear).Clear(); _version++; return this; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + static int Apply(int shiftCount, int fromIndex, Span thisSpan) + where TVector : ISimdVector + { + ref byte p = ref MemoryMarshal.GetReference(thisSpan); + int carryCount = BitsPerByte - shiftCount; + + int toIndex = thisSpan.Length; + + while (fromIndex >= TVector.ElementCount + 1) + { + TVector hi = TVector.LoadUnsafe(ref p, (nuint)(fromIndex -= TVector.ElementCount)) << shiftCount; + TVector lo = TVector.LoadUnsafe(ref p, (nuint)(fromIndex - 1)) >>> carryCount; + TVector result = hi | lo; + result.StoreUnsafe(ref p, (nuint)(toIndex -= TVector.ElementCount)); + } + + return toIndex; + } } /// From a48270170184f1b9b43903051c9ac86b56e73f95 Mon Sep 17 00:00:00 2001 From: kzrnm Date: Thu, 3 Jul 2025 20:47:26 +0900 Subject: [PATCH 03/10] ClearHighExtraBits --- .../tests/BitArray/BitArray_OperatorsTests.cs | 9 +++++++++ .../src/System/Collections/BitArray.cs | 1 + 2 files changed, 10 insertions(+) diff --git a/src/libraries/System.Collections/tests/BitArray/BitArray_OperatorsTests.cs b/src/libraries/System.Collections/tests/BitArray/BitArray_OperatorsTests.cs index 9d8e91f46a8c1f..c6957d75a0d480 100644 --- a/src/libraries/System.Collections/tests/BitArray/BitArray_OperatorsTests.cs +++ b/src/libraries/System.Collections/tests/BitArray/BitArray_OperatorsTests.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; +using System.Runtime.InteropServices; using Xunit; namespace System.Collections.Tests @@ -310,6 +311,14 @@ public static void LeftShift(int length, IEnumerable set, int shift) int index = 0; Assert.All(ba.Cast(), bit => Assert.Equal(expected[index++], bit)); + + (int byteIndex, int bitOffeset) = Math.DivRem(length, BitsPerByte); + if (bitOffeset != 0) + { + Span bs = CollectionsMarshal.AsBytes(ba); + Assert.Equal(byteIndex + 1, bs.Length); + Assert.Equal(0, bs[byteIndex] >> bitOffeset); + } } private static bool[] GetBoolArray(int length, IEnumerable set) diff --git a/src/libraries/System.Private.CoreLib/src/System/Collections/BitArray.cs b/src/libraries/System.Private.CoreLib/src/System/Collections/BitArray.cs index e79a3ef2727d95..141ed209a0fd5d 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Collections/BitArray.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Collections/BitArray.cs @@ -676,6 +676,7 @@ public BitArray LeftShift(int count) } thisSpan.Slice(0, lengthToClear).Clear(); + ClearHighExtraBits(); _version++; return this; From e96a2b8758ba2046c458c23dd6da5f12a025d40d Mon Sep 17 00:00:00 2001 From: kzrnm Date: Fri, 4 Jul 2025 01:59:53 +0900 Subject: [PATCH 04/10] Update logic of Int32 --- .../src/System/Collections/BitArray.cs | 27 ++++++++++++------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/Collections/BitArray.cs b/src/libraries/System.Private.CoreLib/src/System/Collections/BitArray.cs index 141ed209a0fd5d..7c10f711dea3ba 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Collections/BitArray.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Collections/BitArray.cs @@ -540,21 +540,25 @@ public BitArray RightShift(int count) } fromIndex += toIndex; + int carryCount = BitsPerByte - shiftCount; + ref byte p = ref MemoryMarshal.GetReference(thisSpan); - int carry32Count = BitsPerInt32 - shiftCount; + const uint shiftUnit = 0x01010101u; + uint shiftMask = (shiftUnit << carryCount) - shiftUnit; + uint carryMask = ~shiftMask; + while (fromIndex < thisSpan.Length - 4) { - int lo = ReverseIfBE(Unsafe.ReadUnaligned(ref Unsafe.AddByteOffset(ref p, (uint)fromIndex))) >>> shiftCount; - int hi = Unsafe.AddByteOffset(ref p, (uint)(fromIndex + 4)) << carry32Count; - int result = ReverseIfBE(hi | lo); + uint lo = (Unsafe.ReadUnaligned(ref Unsafe.AddByteOffset(ref p, (uint)fromIndex)) >>> shiftCount) & shiftMask; + uint hi = (Unsafe.ReadUnaligned(ref Unsafe.AddByteOffset(ref p, (uint)(fromIndex + 1))) << carryCount) & carryMask; + uint result = hi | lo; Unsafe.WriteUnaligned(ref Unsafe.AddByteOffset(ref p, toIndex), result); fromIndex += 4; toIndex += 4; } - int carryCount = BitsPerByte - shiftCount; while (fromIndex < thisSpan.Length) { int lo = thisSpan[fromIndex] >>> shiftCount; @@ -645,14 +649,19 @@ public BitArray LeftShift(int count) } fromIndex = toIndex - lengthToClear; + int carryCount = BitsPerByte - shiftCount; + ref byte p = ref MemoryMarshal.GetReference(thisSpan); - int carryCount = BitsPerByte - shiftCount; + const uint shiftUnit = 0x01010101u; + uint carryMask = (shiftUnit << shiftCount) - shiftUnit; + uint shiftMask = ~carryMask; + while (fromIndex >= 5) { - int hi = ReverseIfBE(Unsafe.ReadUnaligned(ref Unsafe.AddByteOffset(ref p, (uint)(fromIndex -= 4)))) << shiftCount; - int lo = Unsafe.AddByteOffset(ref p, (uint)(fromIndex - 1)) >>> carryCount; - int result = ReverseIfBE(hi | lo); + uint lo = (Unsafe.ReadUnaligned(ref Unsafe.AddByteOffset(ref p, (uint)(fromIndex -= 4))) << shiftCount) & shiftMask; + uint hi = (Unsafe.ReadUnaligned(ref Unsafe.AddByteOffset(ref p, (uint)(fromIndex - 1))) >>> carryCount) & carryMask; + uint result = hi | lo; Unsafe.WriteUnaligned(ref Unsafe.AddByteOffset(ref p, toIndex -= 4), result); } From d8649826f3be83d656368b1254aeaf047f1e2697 Mon Sep 17 00:00:00 2001 From: kzrnm Date: Tue, 6 Jan 2026 20:29:53 +0900 Subject: [PATCH 05/10] Fix typo Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../tests/BitArray/BitArray_OperatorsTests.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/libraries/System.Collections/tests/BitArray/BitArray_OperatorsTests.cs b/src/libraries/System.Collections/tests/BitArray/BitArray_OperatorsTests.cs index c6957d75a0d480..eeb54d8476cb45 100644 --- a/src/libraries/System.Collections/tests/BitArray/BitArray_OperatorsTests.cs +++ b/src/libraries/System.Collections/tests/BitArray/BitArray_OperatorsTests.cs @@ -312,12 +312,12 @@ public static void LeftShift(int length, IEnumerable set, int shift) int index = 0; Assert.All(ba.Cast(), bit => Assert.Equal(expected[index++], bit)); - (int byteIndex, int bitOffeset) = Math.DivRem(length, BitsPerByte); - if (bitOffeset != 0) + (int byteIndex, int bitOffset) = Math.DivRem(length, BitsPerByte); + if (bitOffset != 0) { Span bs = CollectionsMarshal.AsBytes(ba); Assert.Equal(byteIndex + 1, bs.Length); - Assert.Equal(0, bs[byteIndex] >> bitOffeset); + Assert.Equal(0, bs[byteIndex] >> bitOffset); } } From f6941e208b4bb028edc2349ffc9353ca1ac89053 Mon Sep 17 00:00:00 2001 From: kzrnm Date: Tue, 6 Jan 2026 20:31:09 +0900 Subject: [PATCH 06/10] Separate decrement and store Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../System.Private.CoreLib/src/System/Collections/BitArray.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/Collections/BitArray.cs b/src/libraries/System.Private.CoreLib/src/System/Collections/BitArray.cs index 37d2693fdc00eb..2b7d01dd7e3ed0 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Collections/BitArray.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Collections/BitArray.cs @@ -704,7 +704,8 @@ static int Apply(int shiftCount, int fromIndex, Span thisSpan) TVector hi = TVector.LoadUnsafe(ref p, (nuint)(fromIndex -= TVector.ElementCount)) << shiftCount; TVector lo = TVector.LoadUnsafe(ref p, (nuint)(fromIndex - 1)) >>> carryCount; TVector result = hi | lo; - result.StoreUnsafe(ref p, (nuint)(toIndex -= TVector.ElementCount)); + toIndex -= TVector.ElementCount; + result.StoreUnsafe(ref p, (nuint)toIndex); } return toIndex; From 8093d416188d6925706ffe4d469b1a9b527cc00c Mon Sep 17 00:00:00 2001 From: kzrnm Date: Tue, 6 Jan 2026 20:31:32 +0900 Subject: [PATCH 07/10] Separate decrement and store Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../System.Private.CoreLib/src/System/Collections/BitArray.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/Collections/BitArray.cs b/src/libraries/System.Private.CoreLib/src/System/Collections/BitArray.cs index 2b7d01dd7e3ed0..b74e4878847556 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Collections/BitArray.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Collections/BitArray.cs @@ -701,7 +701,8 @@ static int Apply(int shiftCount, int fromIndex, Span thisSpan) while (fromIndex >= TVector.ElementCount + 1) { - TVector hi = TVector.LoadUnsafe(ref p, (nuint)(fromIndex -= TVector.ElementCount)) << shiftCount; + fromIndex -= TVector.ElementCount; + TVector hi = TVector.LoadUnsafe(ref p, (nuint)fromIndex) << shiftCount; TVector lo = TVector.LoadUnsafe(ref p, (nuint)(fromIndex - 1)) >>> carryCount; TVector result = hi | lo; toIndex -= TVector.ElementCount; From 5beaa6a57639ddaf00801f7cd3105983e09b42b8 Mon Sep 17 00:00:00 2001 From: kzrnm Date: Tue, 6 Jan 2026 20:31:46 +0900 Subject: [PATCH 08/10] Separate decrement and store Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../System.Private.CoreLib/src/System/Collections/BitArray.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/Collections/BitArray.cs b/src/libraries/System.Private.CoreLib/src/System/Collections/BitArray.cs index b74e4878847556..801e7618c4788c 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Collections/BitArray.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Collections/BitArray.cs @@ -663,7 +663,8 @@ public BitArray LeftShift(int count) uint lo = (Unsafe.ReadUnaligned(ref Unsafe.AddByteOffset(ref p, (uint)(fromIndex -= 4))) << shiftCount) & shiftMask; uint hi = (Unsafe.ReadUnaligned(ref Unsafe.AddByteOffset(ref p, (uint)(fromIndex - 1))) >>> carryCount) & carryMask; uint result = hi | lo; - Unsafe.WriteUnaligned(ref Unsafe.AddByteOffset(ref p, toIndex -= 4), result); + toIndex -= 4; + Unsafe.WriteUnaligned(ref Unsafe.AddByteOffset(ref p, toIndex), result); } while (--fromIndex >= 0) From 0c7bb1a4338fcc8f76c3fdcad109270efce329a1 Mon Sep 17 00:00:00 2001 From: kzrnm Date: Tue, 6 Jan 2026 20:31:58 +0900 Subject: [PATCH 09/10] Separate decrement and store Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../System.Private.CoreLib/src/System/Collections/BitArray.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/Collections/BitArray.cs b/src/libraries/System.Private.CoreLib/src/System/Collections/BitArray.cs index 801e7618c4788c..9287899a8033f2 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Collections/BitArray.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Collections/BitArray.cs @@ -660,7 +660,8 @@ public BitArray LeftShift(int count) while (fromIndex >= 5) { - uint lo = (Unsafe.ReadUnaligned(ref Unsafe.AddByteOffset(ref p, (uint)(fromIndex -= 4))) << shiftCount) & shiftMask; + fromIndex -= 4; + uint lo = (Unsafe.ReadUnaligned(ref Unsafe.AddByteOffset(ref p, (uint)fromIndex)) << shiftCount) & shiftMask; uint hi = (Unsafe.ReadUnaligned(ref Unsafe.AddByteOffset(ref p, (uint)(fromIndex - 1))) >>> carryCount) & carryMask; uint result = hi | lo; toIndex -= 4; From e402f97be98b70172327853e5fac8df998c0da28 Mon Sep 17 00:00:00 2001 From: kzrnm Date: Wed, 18 Feb 2026 02:29:18 +0900 Subject: [PATCH 10/10] Avoid unsafe --- .../src/System/Collections/BitArray.cs | 72 ++++++++++++------- 1 file changed, 45 insertions(+), 27 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/Collections/BitArray.cs b/src/libraries/System.Private.CoreLib/src/System/Collections/BitArray.cs index 25c9265b936237..a25bd8fae9dc76 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Collections/BitArray.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Collections/BitArray.cs @@ -543,25 +543,27 @@ public BitArray RightShift(int count) } fromIndex += toIndex; - int carryCount = BitsPerByte - shiftCount; - - ref byte p = ref MemoryMarshal.GetReference(thisSpan); + // 32 bits + ReadOnlySpan intSpanFrom = MemoryMarshal.Cast(thisSpan.Slice(fromIndex)); + Span intSpanTo = MemoryMarshal.Cast(thisSpan.Slice(toIndex)); - const uint shiftUnit = 0x01010101u; - uint shiftMask = (shiftUnit << carryCount) - shiftUnit; - uint carryMask = ~shiftMask; + Debug.Assert(intSpanFrom.Length <= intSpanTo.Length); - while (fromIndex < thisSpan.Length - 4) + int index32; + for (index32 = 0; index32 + 1 < intSpanFrom.Length; index32++) { - uint lo = (Unsafe.ReadUnaligned(ref Unsafe.AddByteOffset(ref p, (uint)fromIndex)) >>> shiftCount) & shiftMask; - uint hi = (Unsafe.ReadUnaligned(ref Unsafe.AddByteOffset(ref p, (uint)(fromIndex + 1))) << carryCount) & carryMask; - uint result = hi | lo; - Unsafe.WriteUnaligned(ref Unsafe.AddByteOffset(ref p, toIndex), result); - - fromIndex += 4; - toIndex += 4; + int lo = ReverseIfBE(intSpanFrom[index32]) >>> shiftCount; + int hi = ReverseIfBE(intSpanFrom[index32 + 1]) << (BitsPerInt32 - shiftCount); + intSpanTo[index32] = ReverseIfBE(hi | lo); } + int size32 = index32 * sizeof(int); + fromIndex += size32; + toIndex += size32; + + // remaining bytes + int carryCount = BitsPerByte - shiftCount; + while (fromIndex < thisSpan.Length) { int lo = thisSpan[fromIndex] >>> shiftCount; @@ -652,24 +654,40 @@ public BitArray LeftShift(int count) } fromIndex = toIndex - lengthToClear; - int carryCount = BitsPerByte - shiftCount; - - ref byte p = ref MemoryMarshal.GetReference(thisSpan); + // 32 bits + const int indexMask = sizeof(int) - 1; + ReadOnlySpan intSpanFrom = MemoryMarshal.Cast(thisSpan.Slice(fromIndex & indexMask, fromIndex & ~indexMask)); + Span intSpanTo = MemoryMarshal.Cast(thisSpan.Slice(toIndex & indexMask, toIndex & ~indexMask)); - const uint shiftUnit = 0x01010101u; - uint carryMask = (shiftUnit << shiftCount) - shiftUnit; - uint shiftMask = ~carryMask; + if (intSpanFrom.Length == 0 || intSpanTo.Length == 0) + { + intSpanFrom = default; + intSpanTo = default; + } + else if (intSpanFrom.Length > intSpanTo.Length) + { + intSpanFrom = intSpanFrom.Slice(intSpanFrom.Length - (intSpanTo.Length + 1), intSpanTo.Length + 1); + } + else + { + intSpanTo = intSpanTo.Slice(intSpanTo.Length - (intSpanFrom.Length - 1), intSpanFrom.Length - 1); + } + Debug.Assert(intSpanFrom.Length == intSpanTo.Length + 1 || intSpanTo.Length == 0); - while (fromIndex >= 5) + for (int i = intSpanTo.Length - 1; i >= 0; i--) { - fromIndex -= 4; - uint lo = (Unsafe.ReadUnaligned(ref Unsafe.AddByteOffset(ref p, (uint)fromIndex)) << shiftCount) & shiftMask; - uint hi = (Unsafe.ReadUnaligned(ref Unsafe.AddByteOffset(ref p, (uint)(fromIndex - 1))) >>> carryCount) & carryMask; - uint result = hi | lo; - toIndex -= 4; - Unsafe.WriteUnaligned(ref Unsafe.AddByteOffset(ref p, toIndex), result); + int hi = ReverseIfBE(intSpanFrom[i + 1]) << shiftCount; + int lo = ReverseIfBE(intSpanFrom[i]) >>> (BitsPerInt32 - shiftCount); + intSpanTo[i] = ReverseIfBE(hi | lo); } + int size32 = intSpanTo.Length * sizeof(int); + fromIndex -= size32; + toIndex -= size32; + + // remaining bytes + int carryCount = BitsPerByte - shiftCount; + while (--fromIndex >= 0) { int hi = thisSpan[fromIndex] << shiftCount;