diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/GeneratorTests.Helpers.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/GeneratorTests.Helpers.cs
index 2a79ea181c89e9..1e245d516d2653 100644
--- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/GeneratorTests.Helpers.cs
+++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/GeneratorTests.Helpers.cs
@@ -68,7 +68,6 @@ private static class Diagnostics
}
private static readonly Assembly[] s_compilationAssemblyRefs = new[] {
- typeof(BitArray).Assembly,
typeof(ConfigurationBinder).Assembly,
typeof(ConfigurationBuilder).Assembly,
typeof(CultureInfo).Assembly,
@@ -80,6 +79,7 @@ private static class Diagnostics
typeof(IDictionary).Assembly,
typeof(OptionsBuilder<>).Assembly,
typeof(OptionsConfigurationServiceCollectionExtensions).Assembly,
+ typeof(Stack<>).Assembly,
typeof(Uri).Assembly,
};
diff --git a/src/libraries/System.Collections/ref/System.Collections.cs b/src/libraries/System.Collections/ref/System.Collections.cs
index 186f5d64cad1dd..539f96f5c480c6 100644
--- a/src/libraries/System.Collections/ref/System.Collections.cs
+++ b/src/libraries/System.Collections/ref/System.Collections.cs
@@ -7,35 +7,6 @@
#if !BUILDING_CORELIB_REFERENCE
namespace System.Collections
{
- public sealed partial class BitArray : System.Collections.ICollection, System.Collections.IEnumerable, System.ICloneable
- {
- public BitArray(bool[] values) { }
- public BitArray(byte[] bytes) { }
- public BitArray(System.Collections.BitArray bits) { }
- public BitArray(int length) { }
- public BitArray(int length, bool defaultValue) { }
- public BitArray(int[] values) { }
- public int Count { get { throw null; } }
- public bool IsReadOnly { get { throw null; } }
- public bool IsSynchronized { get { throw null; } }
- public bool this[int index] { get { throw null; } set { } }
- public int Length { get { throw null; } set { } }
- public object SyncRoot { get { throw null; } }
- public System.Collections.BitArray And(System.Collections.BitArray value) { throw null; }
- public object Clone() { throw null; }
- public void CopyTo(System.Array array, int index) { }
- public bool Get(int index) { throw null; }
- public System.Collections.IEnumerator GetEnumerator() { throw null; }
- public bool HasAllSet() { throw null; }
- public bool HasAnySet() { throw null; }
- public System.Collections.BitArray LeftShift(int count) { throw null; }
- public System.Collections.BitArray Not() { throw null; }
- public System.Collections.BitArray Or(System.Collections.BitArray value) { throw null; }
- public System.Collections.BitArray RightShift(int count) { throw null; }
- public void Set(int index, bool value) { }
- public void SetAll(bool value) { }
- public System.Collections.BitArray Xor(System.Collections.BitArray value) { throw null; }
- }
public static partial class StructuralComparisons
{
public static System.Collections.IComparer StructuralComparer { get { throw null; } }
@@ -584,6 +555,38 @@ void System.Collections.IEnumerator.Reset() { }
}
}
#endif // !BUILDING_CORELIB_REFERENCE
+namespace System.Collections
+{
+ public sealed partial class BitArray : System.Collections.ICollection, System.Collections.IEnumerable, System.ICloneable
+ {
+ public BitArray(bool[] values) { }
+ public BitArray(byte[] bytes) { }
+ public BitArray(System.Collections.BitArray bits) { }
+ public BitArray(int length) { }
+ public BitArray(int length, bool defaultValue) { }
+ public BitArray(int[] values) { }
+ public int Count { get { throw null; } }
+ public bool IsReadOnly { get { throw null; } }
+ public bool IsSynchronized { get { throw null; } }
+ public bool this[int index] { get { throw null; } set { } }
+ public int Length { get { throw null; } set { } }
+ public object SyncRoot { get { throw null; } }
+ public System.Collections.BitArray And(System.Collections.BitArray value) { throw null; }
+ public object Clone() { throw null; }
+ public void CopyTo(System.Array array, int index) { }
+ public bool Get(int index) { throw null; }
+ public System.Collections.IEnumerator GetEnumerator() { throw null; }
+ public bool HasAllSet() { throw null; }
+ public bool HasAnySet() { throw null; }
+ public System.Collections.BitArray LeftShift(int count) { throw null; }
+ public System.Collections.BitArray Not() { throw null; }
+ public System.Collections.BitArray Or(System.Collections.BitArray value) { throw null; }
+ public System.Collections.BitArray RightShift(int count) { throw null; }
+ public void Set(int index, bool value) { }
+ public void SetAll(bool value) { }
+ public System.Collections.BitArray Xor(System.Collections.BitArray value) { throw null; }
+ }
+}
namespace System.Collections.Generic
{
public static partial class CollectionExtensions
diff --git a/src/libraries/System.Collections/src/Resources/Strings.resx b/src/libraries/System.Collections/src/Resources/Strings.resx
index 1e6e014ed65502..2c580ebd632741 100644
--- a/src/libraries/System.Collections/src/Resources/Strings.resx
+++ b/src/libraries/System.Collections/src/Resources/Strings.resx
@@ -102,12 +102,6 @@
Mutating a value collection derived from a dictionary is not allowed.
-
- Array lengths must be the same.
-
-
- Only supported array types for CopyTo on BitArrays are Boolean[], Int32[] and Byte[].
-
Hashtable's capacity overflowed and went negative. Check load factor, capacity and the current size of the table.
@@ -117,9 +111,6 @@
Only single dimensional arrays are supported for the requested action.
-
- The input array length must not exceed Int32.MaxValue / {0}. Otherwise BitArray.Length would exceed Int32.MaxValue.
-
Target array type is not compatible with the type of items in the collection.
diff --git a/src/libraries/System.Collections/src/System.Collections.csproj b/src/libraries/System.Collections/src/System.Collections.csproj
index 8e78d63745bf70..bb7cfe2ad83cfb 100644
--- a/src/libraries/System.Collections/src/System.Collections.csproj
+++ b/src/libraries/System.Collections/src/System.Collections.csproj
@@ -10,7 +10,6 @@
-
diff --git a/src/libraries/System.Collections/src/System/Collections/BitArray.cs b/src/libraries/System.Collections/src/System/Collections/BitArray.cs
deleted file mode 100644
index fa515728e085d4..00000000000000
--- a/src/libraries/System.Collections/src/System/Collections/BitArray.cs
+++ /dev/null
@@ -1,1184 +0,0 @@
-// 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;
-using System.Runtime.Intrinsics.Arm;
-using System.Runtime.Intrinsics.X86;
-
-namespace System.Collections
-{
- // A vector of bits. Use this to store bits efficiently, without having to do bit
- // shifting yourself.
- [Serializable]
- [System.Runtime.CompilerServices.TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
- public sealed class BitArray : ICollection, ICloneable
- {
- private int[] m_array; // Do not rename (binary serialization)
- private int m_length; // Do not rename (binary serialization)
- private int _version; // Do not rename (binary serialization)
-
- private const int _ShrinkThreshold = 256;
-
- /*=========================================================================
- ** Allocates space to hold length bit values. All of the values in the bit
- ** array are set to false.
- **
- ** Exceptions: ArgumentException if length < 0.
- =========================================================================*/
- public BitArray(int length)
- : this(length, false)
- {
- }
-
- /*=========================================================================
- ** Allocates space to hold length bit values. All of the values in the bit
- ** array are set to defaultValue.
- **
- ** Exceptions: ArgumentOutOfRangeException if length < 0.
- =========================================================================*/
- public BitArray(int length, bool defaultValue)
- {
- ArgumentOutOfRangeException.ThrowIfNegative(length);
-
- m_array = new int[GetInt32ArrayLengthFromBitLength(length)];
- m_length = length;
-
- if (defaultValue)
- {
- Array.Fill(m_array, -1);
-
- // clear high bit values in the last int
- Div32Rem(length, out int extraBits);
- if (extraBits > 0)
- {
- m_array[^1] = (1 << extraBits) - 1;
- }
- }
-
- _version = 0;
- }
-
- /*=========================================================================
- ** Allocates space to hold the bit values in bytes. bytes[0] represents
- ** bits 0 - 7, bytes[1] represents bits 8 - 15, etc. The LSB of each byte
- ** represents the lowest index value; bytes[0] & 1 represents bit 0,
- ** bytes[0] & 2 represents bit 1, bytes[0] & 4 represents bit 2, etc.
- **
- ** Exceptions: ArgumentException if bytes == null.
- =========================================================================*/
- public BitArray(byte[] bytes)
- {
- ArgumentNullException.ThrowIfNull(bytes);
-
- // this value is chosen to prevent overflow when computing m_length.
- // m_length is of type int32 and is exposed as a property, so
- // type of m_length can't be changed to accommodate.
- if (bytes.Length > int.MaxValue / BitsPerByte)
- {
- throw new ArgumentException(SR.Format(SR.Argument_ArrayTooLarge, BitsPerByte), nameof(bytes));
- }
-
- m_array = new int[GetInt32ArrayLengthFromByteLength(bytes.Length)];
- m_length = bytes.Length * BitsPerByte;
-
- uint totalCount = (uint)bytes.Length / 4;
-
- ReadOnlySpan byteSpan = bytes;
- for (int i = 0; i < totalCount; i++)
- {
- m_array[i] = BinaryPrimitives.ReadInt32LittleEndian(byteSpan);
- byteSpan = byteSpan.Slice(4);
- }
-
- Debug.Assert(byteSpan.Length >= 0 && byteSpan.Length < 4);
-
- int last = 0;
- switch (byteSpan.Length)
- {
- case 3:
- last = byteSpan[2] << 16;
- goto case 2;
- // fall through
- case 2:
- last |= byteSpan[1] << 8;
- goto case 1;
- // fall through
- case 1:
- m_array[totalCount] = last | byteSpan[0];
- break;
- }
-
- _version = 0;
- }
-
- public BitArray(bool[] values)
- {
- ArgumentNullException.ThrowIfNull(values);
-
- m_array = new int[GetInt32ArrayLengthFromBitLength(values.Length)];
- m_length = values.Length;
-
- uint i = 0;
-
- if (values.Length < Vector256.Count)
- {
- goto LessThan32;
- }
-
- // Comparing with 1s would get rid of the final negation, however this would not work for some CLR bools
- // (true for any non-zero values, false for 0) - any values between 2-255 will be interpreted as false.
- // Instead, We compare with zeroes (== false) then negate the result to ensure compatibility.
-
- ref byte value = ref Unsafe.As(ref MemoryMarshal.GetArrayDataReference(values));
- if (Vector512.IsHardwareAccelerated)
- {
- for (; i <= (uint)values.Length - Vector512.Count; i += (uint)Vector512.Count)
- {
- Vector512 vector = Vector512.LoadUnsafe(ref value, i);
- Vector512 isFalse = Vector512.Equals(vector, Vector512.Zero);
-
- ulong result = isFalse.ExtractMostSignificantBits();
- m_array[i / 32u] = (int)(~result & 0x00000000FFFFFFFF);
- m_array[(i / 32u) + 1] = (int)((~result >> 32) & 0x00000000FFFFFFFF);
- }
- }
- else if (Vector256.IsHardwareAccelerated)
- {
- for (; i <= (uint)values.Length - Vector256.Count; i += (uint)Vector256.Count)
- {
- Vector256 vector = Vector256.LoadUnsafe(ref value, i);
- Vector256 isFalse = Vector256.Equals(vector, Vector256.Zero);
-
- uint result = isFalse.ExtractMostSignificantBits();
- m_array[i / 32u] = (int)(~result);
- }
- }
- else if (Vector128.IsHardwareAccelerated)
- {
- for (; i <= (uint)values.Length - Vector128.Count * 2u; i += (uint)Vector128.Count * 2u)
- {
- Vector128 lowerVector = Vector128.LoadUnsafe(ref value, i);
- Vector128 lowerIsFalse = Vector128.Equals(lowerVector, Vector128.Zero);
- uint lowerResult = lowerIsFalse.ExtractMostSignificantBits();
-
- Vector128 upperVector = Vector128.LoadUnsafe(ref value, i + (uint)Vector128.Count);
- Vector128 upperIsFalse = Vector128.Equals(upperVector, Vector128.Zero);
- uint upperResult = upperIsFalse.ExtractMostSignificantBits();
-
- m_array[i / 32u] = (int)(~((upperResult << 16) | lowerResult));
- }
- }
-
- LessThan32:
- for (; i < (uint)values.Length; i++)
- {
- if (values[i])
- {
- int elementIndex = Div32Rem((int)i, out int extraBits);
- m_array[elementIndex] |= 1 << extraBits;
- }
- }
-
- _version = 0;
- }
-
- /*=========================================================================
- ** Allocates space to hold the bit values in values. values[0] represents
- ** bits 0 - 31, values[1] represents bits 32 - 63, etc. The LSB of each
- ** integer represents the lowest index value; values[0] & 1 represents bit
- ** 0, values[0] & 2 represents bit 1, values[0] & 4 represents bit 2, etc.
- **
- ** Exceptions: ArgumentException if values == null.
- =========================================================================*/
- public BitArray(int[] values)
- {
- ArgumentNullException.ThrowIfNull(values);
-
- // this value is chosen to prevent overflow when computing m_length
- if (values.Length > int.MaxValue / BitsPerInt32)
- {
- throw new ArgumentException(SR.Format(SR.Argument_ArrayTooLarge, BitsPerInt32), nameof(values));
- }
-
- m_array = new int[values.Length];
- Array.Copy(values, m_array, values.Length);
- m_length = values.Length * BitsPerInt32;
-
- _version = 0;
- }
-
- /*=========================================================================
- ** Allocates a new BitArray with the same length and bit values as bits.
- **
- ** Exceptions: ArgumentException if bits == null.
- =========================================================================*/
- public BitArray(BitArray bits)
- {
- ArgumentNullException.ThrowIfNull(bits);
-
- int arrayLength = GetInt32ArrayLengthFromBitLength(bits.m_length);
-
- m_array = new int[arrayLength];
-
- Debug.Assert(bits.m_array.Length <= arrayLength);
-
- Array.Copy(bits.m_array, m_array, arrayLength);
- m_length = bits.m_length;
-
- _version = bits._version;
- }
-
- public bool this[int index]
- {
- get => Get(index);
- set => Set(index, value);
- }
-
- /*=========================================================================
- ** Returns the bit value at position index.
- **
- ** Exceptions: ArgumentOutOfRangeException if index < 0 or
- ** index >= GetLength().
- =========================================================================*/
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public bool Get(int index)
- {
- if ((uint)index >= (uint)m_length)
- ThrowArgumentOutOfRangeException(index);
-
- return (m_array[index >> 5] & (1 << index)) != 0;
- }
-
- /*=========================================================================
- ** Sets the bit value at position index to value.
- **
- ** Exceptions: ArgumentOutOfRangeException if index < 0 or
- ** index >= GetLength().
- =========================================================================*/
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public void Set(int index, bool value)
- {
- if ((uint)index >= (uint)m_length)
- ThrowArgumentOutOfRangeException(index);
-
- int bitMask = 1 << index;
- ref int segment = ref m_array[index >> 5];
-
- if (value)
- {
- segment |= bitMask;
- }
- else
- {
- segment &= ~bitMask;
- }
-
- _version++;
- }
-
- /*=========================================================================
- ** Sets all the bit values to value.
- =========================================================================*/
- public void SetAll(bool value)
- {
- int arrayLength = GetInt32ArrayLengthFromBitLength(Length);
- Span span = m_array.AsSpan(0, arrayLength);
- if (value)
- {
- span.Fill(-1);
-
- // clear high bit values in the last int
- Div32Rem(m_length, out int extraBits);
- if (extraBits > 0)
- {
- span[^1] &= (1 << extraBits) - 1;
- }
- }
- else
- {
- span.Clear();
- }
-
- _version++;
- }
-
- /*=========================================================================
- ** Returns a reference to the current instance ANDed with value.
- **
- ** Exceptions: ArgumentException if value == null or
- ** value.Length != this.Length.
- =========================================================================*/
- public BitArray And(BitArray value)
- {
- ArgumentNullException.ThrowIfNull(value);
-
- // This method uses unsafe code to manipulate data in the BitArrays. To avoid issues with
- // buggy code concurrently mutating these instances in a way that could cause memory corruption,
- // we snapshot the arrays from both and then operate only on those snapshots, while also validating
- // that the count we iterate to is within the bounds of both arrays. We don't care about such code
- // corrupting the BitArray data in a way that produces incorrect answers, since BitArray is not meant
- // to be thread-safe; we only care about avoiding buffer overruns.
- int[] thisArray = m_array;
- int[] valueArray = value.m_array;
-
- int count = GetInt32ArrayLengthFromBitLength(Length);
- if (Length != value.Length || (uint)count > (uint)thisArray.Length || (uint)count > (uint)valueArray.Length)
- throw new ArgumentException(SR.Arg_ArrayLengthsDiffer);
-
- // Unroll loop for count less than Vector256 size.
- switch (count)
- {
- case 7: thisArray[6] &= valueArray[6]; goto case 6;
- case 6: thisArray[5] &= valueArray[5]; goto case 5;
- case 5: thisArray[4] &= valueArray[4]; goto case 4;
- case 4: thisArray[3] &= valueArray[3]; goto case 3;
- case 3: thisArray[2] &= valueArray[2]; goto case 2;
- case 2: thisArray[1] &= valueArray[1]; goto case 1;
- case 1: thisArray[0] &= valueArray[0]; goto Done;
- case 0: goto Done;
- }
-
- uint i = 0;
-
- ref int left = ref MemoryMarshal.GetArrayDataReference(thisArray);
- ref int right = ref MemoryMarshal.GetArrayDataReference(valueArray);
- if (Vector512.IsHardwareAccelerated && (uint)count >= Vector512.Count)
- {
- for (; i < (uint)count - (Vector512.Count - 1u); i += (uint)Vector512.Count)
- {
- Vector512 result = Vector512.LoadUnsafe(ref left, i) & Vector512.LoadUnsafe(ref right, i);
- result.StoreUnsafe(ref left, i);
- }
- }
- else if (Vector256.IsHardwareAccelerated && (uint)count >= Vector256.Count)
- {
- for (; i < (uint)count - (Vector256.Count - 1u); i += (uint)Vector256.Count)
- {
- Vector256 result = Vector256.LoadUnsafe(ref left, i) & Vector256.LoadUnsafe(ref right, i);
- result.StoreUnsafe(ref left, i);
- }
- }
- else if (Vector128.IsHardwareAccelerated && (uint)count >= Vector128.Count)
- {
- for (; i < (uint)count - (Vector128.Count - 1u); i += (uint)Vector128.Count)
- {
- Vector128 result = Vector128.LoadUnsafe(ref left, i) & Vector128.LoadUnsafe(ref right, i);
- result.StoreUnsafe(ref left, i);
- }
- }
-
- for (; i < (uint)count; i++)
- thisArray[i] &= valueArray[i];
-
- Done:
- _version++;
- return this;
- }
-
- /*=========================================================================
- ** Returns a reference to the current instance ORed with value.
- **
- ** Exceptions: ArgumentException if value == null or
- ** value.Length != this.Length.
- =========================================================================*/
- public BitArray Or(BitArray value)
- {
- ArgumentNullException.ThrowIfNull(value);
-
- // This method uses unsafe code to manipulate data in the BitArrays. To avoid issues with
- // buggy code concurrently mutating these instances in a way that could cause memory corruption,
- // we snapshot the arrays from both and then operate only on those snapshots, while also validating
- // that the count we iterate to is within the bounds of both arrays. We don't care about such code
- // corrupting the BitArray data in a way that produces incorrect answers, since BitArray is not meant
- // to be thread-safe; we only care about avoiding buffer overruns.
- int[] thisArray = m_array;
- int[] valueArray = value.m_array;
-
- int count = GetInt32ArrayLengthFromBitLength(Length);
- if (Length != value.Length || (uint)count > (uint)thisArray.Length || (uint)count > (uint)valueArray.Length)
- throw new ArgumentException(SR.Arg_ArrayLengthsDiffer);
-
- // Unroll loop for count less than Vector256 size.
- switch (count)
- {
- case 7: thisArray[6] |= valueArray[6]; goto case 6;
- case 6: thisArray[5] |= valueArray[5]; goto case 5;
- case 5: thisArray[4] |= valueArray[4]; goto case 4;
- case 4: thisArray[3] |= valueArray[3]; goto case 3;
- case 3: thisArray[2] |= valueArray[2]; goto case 2;
- case 2: thisArray[1] |= valueArray[1]; goto case 1;
- case 1: thisArray[0] |= valueArray[0]; goto Done;
- case 0: goto Done;
- }
-
- uint i = 0;
-
- ref int left = ref MemoryMarshal.GetArrayDataReference(thisArray);
- ref int right = ref MemoryMarshal.GetArrayDataReference(valueArray);
- if (Vector512.IsHardwareAccelerated && (uint)count >= Vector512.Count)
- {
- for (; i < (uint)count - (Vector512.Count - 1u); i += (uint)Vector512.Count)
- {
- Vector512 result = Vector512.LoadUnsafe(ref left, i) | Vector512.LoadUnsafe(ref right, i);
- result.StoreUnsafe(ref left, i);
- }
- }
- else if (Vector256.IsHardwareAccelerated && (uint)count >= Vector256.Count)
- {
- for (; i < (uint)count - (Vector256.Count - 1u); i += (uint)Vector256.Count)
- {
- Vector256 result = Vector256.LoadUnsafe(ref left, i) | Vector256.LoadUnsafe(ref right, i);
- result.StoreUnsafe(ref left, i);
- }
- }
- else if (Vector128.IsHardwareAccelerated && (uint)count >= Vector128.Count)
- {
- for (; i < (uint)count - (Vector128.Count - 1u); i += (uint)Vector128.Count)
- {
- Vector128 result = Vector128.LoadUnsafe(ref left, i) | Vector128.LoadUnsafe(ref right, i);
- result.StoreUnsafe(ref left, i);
- }
- }
-
- for (; i < (uint)count; i++)
- thisArray[i] |= valueArray[i];
-
- Done:
- _version++;
- return this;
- }
-
- /*=========================================================================
- ** Returns a reference to the current instance XORed with value.
- **
- ** Exceptions: ArgumentException if value == null or
- ** value.Length != this.Length.
- =========================================================================*/
- public BitArray Xor(BitArray value)
- {
- ArgumentNullException.ThrowIfNull(value);
-
- // This method uses unsafe code to manipulate data in the BitArrays. To avoid issues with
- // buggy code concurrently mutating these instances in a way that could cause memory corruption,
- // we snapshot the arrays from both and then operate only on those snapshots, while also validating
- // that the count we iterate to is within the bounds of both arrays. We don't care about such code
- // corrupting the BitArray data in a way that produces incorrect answers, since BitArray is not meant
- // to be thread-safe; we only care about avoiding buffer overruns.
- int[] thisArray = m_array;
- int[] valueArray = value.m_array;
-
- int count = GetInt32ArrayLengthFromBitLength(Length);
- if (Length != value.Length || (uint)count > (uint)thisArray.Length || (uint)count > (uint)valueArray.Length)
- throw new ArgumentException(SR.Arg_ArrayLengthsDiffer);
-
- // Unroll loop for count less than Vector256 size.
- switch (count)
- {
- case 7: thisArray[6] ^= valueArray[6]; goto case 6;
- case 6: thisArray[5] ^= valueArray[5]; goto case 5;
- case 5: thisArray[4] ^= valueArray[4]; goto case 4;
- case 4: thisArray[3] ^= valueArray[3]; goto case 3;
- case 3: thisArray[2] ^= valueArray[2]; goto case 2;
- case 2: thisArray[1] ^= valueArray[1]; goto case 1;
- case 1: thisArray[0] ^= valueArray[0]; goto Done;
- case 0: goto Done;
- }
-
- uint i = 0;
-
- ref int left = ref MemoryMarshal.GetArrayDataReference(thisArray);
- ref int right = ref MemoryMarshal.GetArrayDataReference(valueArray);
-
- if (Vector512.IsHardwareAccelerated && (uint)count >= Vector512.Count)
- {
- for (; i < (uint)count - (Vector512.Count - 1u); i += (uint)Vector512.Count)
- {
- Vector512 result = Vector512.LoadUnsafe(ref left, i) ^ Vector512.LoadUnsafe(ref right, i);
- result.StoreUnsafe(ref left, i);
- }
- }
- else if (Vector256.IsHardwareAccelerated && (uint)count >= Vector256.Count)
- {
- for (; i < (uint)count - (Vector256.Count - 1u); i += (uint)Vector256.Count)
- {
- Vector256 result = Vector256.LoadUnsafe(ref left, i) ^ Vector256.LoadUnsafe(ref right, i);
- result.StoreUnsafe(ref left, i);
- }
- }
- else if (Vector128.IsHardwareAccelerated && (uint)count >= Vector128.Count)
- {
- for (; i < (uint)count - (Vector128.Count - 1u); i += (uint)Vector128.Count)
- {
- Vector128 result = Vector128.LoadUnsafe(ref left, i) ^ Vector128.LoadUnsafe(ref right, i);
- result.StoreUnsafe(ref left, i);
- }
- }
-
- for (; i < (uint)count; i++)
- thisArray[i] ^= valueArray[i];
-
- Done:
- _version++;
- return this;
- }
-
- /*=========================================================================
- ** Inverts all the bit values. On/true bit values are converted to
- ** off/false. Off/false bit values are turned on/true. The current instance
- ** is updated and returned.
- =========================================================================*/
- public BitArray Not()
- {
- // This method uses unsafe code to manipulate data in the BitArray. To avoid issues with
- // buggy code concurrently mutating this instance in a way that could cause memory corruption,
- // we snapshot the array then operate only on this snapshot. We don't care about such code
- // corrupting the BitArray data in a way that produces incorrect answers, since BitArray is not meant
- // to be thread-safe; we only care about avoiding buffer overruns.
- int[] thisArray = m_array;
-
- int count = GetInt32ArrayLengthFromBitLength(Length);
-
- // Unroll loop for count less than Vector256 size.
- switch (count)
- {
- case 7: thisArray[6] = ~thisArray[6]; goto case 6;
- case 6: thisArray[5] = ~thisArray[5]; goto case 5;
- case 5: thisArray[4] = ~thisArray[4]; goto case 4;
- case 4: thisArray[3] = ~thisArray[3]; goto case 3;
- case 3: thisArray[2] = ~thisArray[2]; goto case 2;
- case 2: thisArray[1] = ~thisArray[1]; goto case 1;
- case 1: thisArray[0] = ~thisArray[0]; goto Done;
- case 0: goto Done;
- }
-
- uint i = 0;
-
- ref int value = ref MemoryMarshal.GetArrayDataReference(thisArray);
- if (Vector512.IsHardwareAccelerated && (uint)count >= Vector512.Count)
- {
- for (; i < (uint)count - (Vector512.Count - 1u); i += (uint)Vector512.Count)
- {
- Vector512 result = ~Vector512.LoadUnsafe(ref value, i);
- result.StoreUnsafe(ref value, i);
- }
- }
- else if (Vector256.IsHardwareAccelerated && (uint)count >= Vector256.Count)
- {
- for (; i < (uint)count - (Vector256.Count - 1u); i += (uint)Vector256.Count)
- {
- Vector256 result = ~Vector256.LoadUnsafe(ref value, i);
- result.StoreUnsafe(ref value, i);
- }
- }
- else if (Vector128.IsHardwareAccelerated && (uint)count >= Vector128.Count)
- {
- for (; i < (uint)count - (Vector128.Count - 1u); i += (uint)Vector128.Count)
- {
- Vector128 result = ~Vector128.LoadUnsafe(ref value, i);
- result.StoreUnsafe(ref value, i);
- }
- }
-
- for (; i < (uint)count; i++)
- thisArray[i] = ~thisArray[i];
-
- Done:
- _version++;
- return this;
- }
-
- /*=========================================================================
- ** Shift all the bit values to right on count bits. The current instance is
- ** updated and returned.
- *
- ** Exceptions: ArgumentOutOfRangeException if count < 0
- =========================================================================*/
- public BitArray RightShift(int count)
- {
- if (count <= 0)
- {
- ArgumentOutOfRangeException.ThrowIfNegative(count);
-
- _version++;
- return this;
- }
-
- int toIndex = 0;
- int ints = GetInt32ArrayLengthFromBitLength(m_length);
- if (count < m_length)
- {
- // We can not use Math.DivRem without taking a dependency on System.Runtime.Extensions
- int fromIndex = Div32Rem(count, out int shiftCount);
- Div32Rem(m_length, out int extraBits);
- if (shiftCount == 0)
- {
- unchecked
- {
- // 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);
- m_array[ints - 1] &= (int)mask;
- }
- Array.Copy(m_array, fromIndex, m_array, 0, ints - fromIndex);
- toIndex = ints - fromIndex;
- }
- else
- {
- int lastIndex = ints - 1;
- unchecked
- {
- while (fromIndex < lastIndex)
- {
- uint right = (uint)m_array[fromIndex] >> shiftCount;
- int left = m_array[++fromIndex] << (BitsPerInt32 - shiftCount);
- m_array[toIndex++] = left | (int)right;
- }
- uint mask = uint.MaxValue >> (BitsPerInt32 - extraBits);
- mask &= (uint)m_array[fromIndex];
- m_array[toIndex++] = (int)(mask >> shiftCount);
- }
- }
- }
-
- m_array.AsSpan(toIndex, ints - toIndex).Clear();
- _version++;
- return this;
- }
-
- /*=========================================================================
- ** Shift all the bit values to left on count bits. The current instance is
- ** updated and returned.
- *
- ** Exceptions: ArgumentOutOfRangeException if count < 0
- =========================================================================*/
- public BitArray LeftShift(int count)
- {
- if (count <= 0)
- {
- ArgumentOutOfRangeException.ThrowIfNegative(count);
-
- _version++;
- return this;
- }
-
- int lengthToClear;
- if (count < m_length)
- {
- int lastIndex = (m_length - 1) >> BitShiftPerInt32; // Divide by 32.
-
- // We can not use Math.DivRem without taking a dependency on System.Runtime.Extensions
- lengthToClear = Div32Rem(count, out int shiftCount);
-
- if (shiftCount == 0)
- {
- Array.Copy(m_array, 0, m_array, lengthToClear, lastIndex + 1 - lengthToClear);
- }
- else
- {
- int fromindex = lastIndex - lengthToClear;
- unchecked
- {
- while (fromindex > 0)
- {
- int left = m_array[fromindex] << shiftCount;
- uint right = (uint)m_array[--fromindex] >> (BitsPerInt32 - shiftCount);
- m_array[lastIndex] = left | (int)right;
- lastIndex--;
- }
- m_array[lastIndex] = m_array[fromindex] << shiftCount;
- }
- }
- }
- else
- {
- lengthToClear = GetInt32ArrayLengthFromBitLength(m_length); // Clear all
- }
-
- m_array.AsSpan(0, lengthToClear).Clear();
- _version++;
- return this;
- }
-
- public int Length
- {
- get
- {
- return m_length;
- }
- set
- {
- ArgumentOutOfRangeException.ThrowIfNegative(value);
-
- int newints = GetInt32ArrayLengthFromBitLength(value);
- if (newints > m_array.Length || newints + _ShrinkThreshold < m_array.Length)
- {
- // grow or shrink (if wasting more than _ShrinkThreshold ints)
- Array.Resize(ref m_array, newints);
- }
-
- if (value > m_length)
- {
- // clear high bit values in the last int
- int last = (m_length - 1) >> BitShiftPerInt32;
- Div32Rem(m_length, out int bits);
- if (bits > 0)
- {
- m_array[last] &= (1 << bits) - 1;
- }
-
- // clear remaining int values
- m_array.AsSpan(last + 1, newints - last - 1).Clear();
- }
-
- m_length = value;
- _version++;
- }
- }
-
- public unsafe void CopyTo(Array array, int index)
- {
- ArgumentNullException.ThrowIfNull(array);
-
- ArgumentOutOfRangeException.ThrowIfNegative(index);
-
- if (array.Rank != 1)
- throw new ArgumentException(SR.Arg_RankMultiDimNotSupported, nameof(array));
-
- if (array is int[] intArray)
- {
- if (array.Length - index < GetInt32ArrayLengthFromBitLength(m_length))
- {
- throw new ArgumentException(SR.Argument_InvalidOffLen);
- }
-
- int quotient = Div32Rem(m_length, out int extraBits);
-
- Array.Copy(m_array, 0, intArray, index, quotient);
-
- if (extraBits > 0)
- {
- // the last int needs to be masked
- intArray[index + quotient] = m_array[quotient] & unchecked((1 << extraBits) - 1);
- }
- }
- else if (array is byte[] byteArray)
- {
- int arrayLength = GetByteArrayLengthFromBitLength(m_length);
- if ((array.Length - index) < arrayLength)
- {
- throw new ArgumentException(SR.Argument_InvalidOffLen);
- }
-
- // equivalent to m_length % BitsPerByte, since BitsPerByte is a power of 2
- uint extraBits = (uint)m_length & (BitsPerByte - 1);
- if (extraBits > 0)
- {
- // last byte is not aligned, we will directly copy one less byte
- arrayLength -= 1;
- }
-
- Span span = byteArray.AsSpan(index);
-
- int quotient = Div4Rem(arrayLength, out int remainder);
- for (int i = 0; i < quotient; i++)
- {
- BinaryPrimitives.WriteInt32LittleEndian(span, m_array[i]);
- span = span.Slice(4);
- }
-
- if (extraBits > 0)
- {
- Debug.Assert(span.Length > 0);
- Debug.Assert(m_array.Length > quotient);
- // mask the final byte
- span[remainder] = (byte)((m_array[quotient] >> (remainder * 8)) & ((1 << (int)extraBits) - 1));
- }
-
- switch (remainder)
- {
- case 3:
- span[2] = (byte)(m_array[quotient] >> 16);
- goto case 2;
- // fall through
- case 2:
- span[1] = (byte)(m_array[quotient] >> 8);
- goto case 1;
- // fall through
- case 1:
- span[0] = (byte)m_array[quotient];
- break;
- }
- }
- else if (array is bool[] boolArray)
- {
- if (array.Length - index < m_length)
- {
- throw new ArgumentException(SR.Argument_InvalidOffLen);
- }
-
- uint i = 0;
-
- if (m_length < BitsPerInt32)
- goto LessThan32;
-
- // The mask used when shuffling a single int into Vector128/256/512.
- // On little endian machines, the lower 8 bits of int belong in the first byte, next lower 8 in the second and so on.
- // We place the bytes that contain the bits to its respective byte so that we can mask out only the relevant bits later.
- Vector128 lowerShuffleMask_CopyToBoolArray = Vector128.Create(0, 0x01010101_01010101).AsByte();
- Vector128 upperShuffleMask_CopyToBoolArray = Vector128.Create(0x02020202_02020202, 0x03030303_03030303).AsByte();
-
- if (Avx512BW.IsSupported && (uint)m_length >= Vector512.Count)
- {
- Vector256 upperShuffleMask_CopyToBoolArray256 = Vector256.Create(0x04040404_04040404, 0x05050505_05050505,
- 0x06060606_06060606, 0x07070707_07070707).AsByte();
- Vector256 lowerShuffleMask_CopyToBoolArray256 = Vector256.Create(lowerShuffleMask_CopyToBoolArray, upperShuffleMask_CopyToBoolArray);
- Vector512 shuffleMask = Vector512.Create(lowerShuffleMask_CopyToBoolArray256, upperShuffleMask_CopyToBoolArray256);
- Vector512 bitMask = Vector512.Create(0x80402010_08040201).AsByte();
- Vector512 ones = Vector512.Create((byte)1);
-
- fixed (bool* destination = &boolArray[index])
- {
- for (; (i + Vector512.Count) <= (uint)m_length; i += (uint)Vector512.Count)
- {
- ulong bits = (ulong)(uint)m_array[i / (uint)BitsPerInt32] + ((ulong)m_array[(i / (uint)BitsPerInt32) + 1] << BitsPerInt32);
- Vector512 scalar = Vector512.Create(bits);
- Vector512 shuffled = Avx512BW.Shuffle(scalar.AsByte(), shuffleMask);
- Vector512 extracted = Avx512F.And(shuffled, bitMask);
-
- // The extracted bits can be anywhere between 0 and 255, so we normalise the value to either 0 or 1
- // to ensure compatibility with "C# bool" (0 for false, 1 for true, rest undefined)
- Vector512 normalized = Avx512BW.Min(extracted, ones);
- Avx512F.Store((byte*)destination + i, normalized);
- }
- }
- }
- else if (Avx2.IsSupported && (uint)m_length >= Vector256.Count)
- {
- Vector256 shuffleMask = Vector256.Create(lowerShuffleMask_CopyToBoolArray, upperShuffleMask_CopyToBoolArray);
- Vector256 bitMask = Vector256.Create(0x80402010_08040201).AsByte();
- //Internal.Console.WriteLine(bitMask);
- Vector256 ones = Vector256.Create((byte)1);
-
- fixed (bool* destination = &boolArray[index])
- {
- for (; (i + Vector256.Count) <= (uint)m_length; i += (uint)Vector256.Count)
- {
- int bits = m_array[i / (uint)BitsPerInt32];
- Vector256 scalar = Vector256.Create(bits);
- Vector256 shuffled = Avx2.Shuffle(scalar.AsByte(), shuffleMask);
- Vector256 extracted = Avx2.And(shuffled, bitMask);
-
- // The extracted bits can be anywhere between 0 and 255, so we normalise the value to either 0 or 1
- // to ensure compatibility with "C# bool" (0 for false, 1 for true, rest undefined)
- Vector256 normalized = Avx2.Min(extracted, ones);
- Avx.Store((byte*)destination + i, normalized);
- }
- }
- }
- else if (Ssse3.IsSupported && ((uint)m_length >= Vector128.Count * 2u))
- {
- Vector128 lowerShuffleMask = lowerShuffleMask_CopyToBoolArray;
- Vector128 upperShuffleMask = upperShuffleMask_CopyToBoolArray;
- Vector128 ones = Vector128.Create((byte)1);
- Vector128 bitMask128 = BitConverter.IsLittleEndian ?
- Vector128.Create(0x80402010_08040201).AsByte() :
- Vector128.Create(0x01020408_10204080).AsByte();
-
- fixed (bool* destination = &boolArray[index])
- {
- for (; (i + Vector128.Count * 2u) <= (uint)m_length; i += (uint)Vector128.Count * 2u)
- {
- int bits = m_array[i / (uint)BitsPerInt32];
- Vector128 scalar = Vector128.CreateScalarUnsafe(bits);
-
- Vector128 shuffledLower = Ssse3.Shuffle(scalar.AsByte(), lowerShuffleMask);
- Vector128 extractedLower = Sse2.And(shuffledLower, bitMask128);
- Vector128 normalizedLower = Sse2.Min(extractedLower, ones);
- Sse2.Store((byte*)destination + i, normalizedLower);
-
- Vector128 shuffledHigher = Ssse3.Shuffle(scalar.AsByte(), upperShuffleMask);
- Vector128 extractedHigher = Sse2.And(shuffledHigher, bitMask128);
- Vector128 normalizedHigher = Sse2.Min(extractedHigher, ones);
- Sse2.Store((byte*)destination + i + Vector128.Count, normalizedHigher);
- }
- }
- }
- else if (AdvSimd.Arm64.IsSupported)
- {
- Vector128 ones = Vector128.Create((byte)1);
- Vector128 bitMask128 = BitConverter.IsLittleEndian ?
- Vector128.Create(0x80402010_08040201).AsByte() :
- Vector128.Create(0x01020408_10204080).AsByte();
-
- fixed (bool* destination = &boolArray[index])
- {
- for (; (i + Vector128.Count * 2u) <= (uint)m_length; i += (uint)Vector128.Count * 2u)
- {
- int bits = m_array[i / (uint)BitsPerInt32];
- // Same logic as SSSE3 path, except we do not have Shuffle instruction.
- // (TableVectorLookup could be an alternative - dotnet/runtime#1277)
- // Instead we use chained ZIP1/2 instructions:
- // (A0 is the byte containing LSB, A3 is the byte containing MSB)
- // bits (on Big endian) - A3 A2 A1 A0
- // bits (Little endian) / Byte reversal - A0 A1 A2 A3
- // v1 = Vector128.Create - A0 A1 A2 A3 A0 A1 A2 A3 A0 A1 A2 A3 A0 A1 A2 A3
- // v2 = ZipLow(v1, v1) - A0 A0 A1 A1 A2 A2 A3 A3 A0 A0 A1 A1 A2 A2 A3 A3
- // v3 = ZipLow(v2, v2) - A0 A0 A0 A0 A1 A1 A1 A1 A2 A2 A2 A2 A3 A3 A3 A3
- // shuffledLower = ZipLow(v3, v3) - A0 A0 A0 A0 A0 A0 A0 A0 A1 A1 A1 A1 A1 A1 A1 A1
- // shuffledHigher = ZipHigh(v3, v3) - A2 A2 A2 A2 A2 A2 A2 A2 A3 A3 A3 A3 A3 A3 A3 A3
- if (!BitConverter.IsLittleEndian)
- {
- bits = BinaryPrimitives.ReverseEndianness(bits);
- }
- Vector128 vector = Vector128.Create(bits).AsByte();
- vector = AdvSimd.Arm64.ZipLow(vector, vector);
- vector = AdvSimd.Arm64.ZipLow(vector, vector);
-
- Vector128 shuffledLower = AdvSimd.Arm64.ZipLow(vector, vector);
- Vector128 extractedLower = AdvSimd.And(shuffledLower, bitMask128);
- Vector128 normalizedLower = AdvSimd.Min(extractedLower, ones);
-
- Vector128 shuffledHigher = AdvSimd.Arm64.ZipHigh(vector, vector);
- Vector128 extractedHigher = AdvSimd.And(shuffledHigher, bitMask128);
- Vector128 normalizedHigher = AdvSimd.Min(extractedHigher, ones);
-
- AdvSimd.Arm64.StorePair((byte*)destination + i, normalizedLower, normalizedHigher);
- }
- }
- }
-
- LessThan32:
- for (; i < (uint)m_length; i++)
- {
- int elementIndex = Div32Rem((int)i, out int extraBits);
- boolArray[(uint)index + i] = ((m_array[elementIndex] >> extraBits) & 0x00000001) != 0;
- }
- }
- else
- {
- throw new ArgumentException(SR.Arg_BitArrayTypeUnsupported, nameof(array));
- }
- }
-
- ///
- /// Determines whether all bits in the are set to true.
- ///
- /// true if every bit in the is set to true, or if is empty; otherwise, false.
- public bool HasAllSet()
- {
- Div32Rem(m_length, out int extraBits);
- int intCount = GetInt32ArrayLengthFromBitLength(m_length);
- if (extraBits != 0)
- {
- intCount--;
- }
-
- const int AllSetBits = -1; // 0xFF_FF_FF_FF
- if (m_array.AsSpan(0, intCount).ContainsAnyExcept(AllSetBits))
- {
- return false;
- }
-
- if (extraBits == 0)
- {
- return true;
- }
-
- Debug.Assert(GetInt32ArrayLengthFromBitLength(m_length) > 0);
- Debug.Assert(intCount == GetInt32ArrayLengthFromBitLength(m_length) - 1);
-
- int mask = (1 << extraBits) - 1;
- return (m_array[intCount] & mask) == mask;
- }
-
- ///
- /// Determines whether any bit in the is set to true.
- ///
- /// true if is not empty and at least one of its bit is set to true; otherwise, false.
- public bool HasAnySet()
- {
- Div32Rem(m_length, out int extraBits);
- int intCount = GetInt32ArrayLengthFromBitLength(m_length);
- if (extraBits != 0)
- {
- intCount--;
- }
-
- if (m_array.AsSpan(0, intCount).ContainsAnyExcept(0))
- {
- return true;
- }
-
- if (extraBits == 0)
- {
- return false;
- }
-
- Debug.Assert(GetInt32ArrayLengthFromBitLength(m_length) > 0);
- Debug.Assert(intCount == GetInt32ArrayLengthFromBitLength(m_length) - 1);
-
- return (m_array[intCount] & (1 << extraBits) - 1) != 0;
- }
-
- public int Count => m_length;
-
- public object SyncRoot => this;
-
- public bool IsSynchronized => false;
-
- public bool IsReadOnly => false;
-
- public object Clone() => new BitArray(this);
-
- public IEnumerator GetEnumerator() => new BitArrayEnumeratorSimple(this);
-
- // XPerY=n means that n Xs can be stored in 1 Y.
- private const int BitsPerInt32 = 32;
- private const int BitsPerByte = 8;
-
- private const int BitShiftPerInt32 = 5;
- private const int BitShiftPerByte = 3;
- private const int BitShiftForBytesPerInt32 = 2;
-
- ///
- /// Used for conversion between different representations of bit array.
- /// Returns (n + (32 - 1)) / 32, rearranged to avoid arithmetic overflow.
- /// For example, in the bit to int case, the straightforward calc would
- /// be (n + 31) / 32, but that would cause overflow. So instead it's
- /// rearranged to ((n - 1) / 32) + 1.
- /// Due to sign extension, we don't need to special case for n == 0, if we use
- /// bitwise operations (since ((n - 1) >> 5) + 1 = 0).
- /// This doesn't hold true for ((n - 1) / 32) + 1, which equals 1.
- ///
- /// Usage:
- /// GetArrayLength(77): returns how many ints must be
- /// allocated to store 77 bits.
- ///
- ///
- /// how many ints are required to store n bytes
- private static int GetInt32ArrayLengthFromBitLength(int n)
- {
- Debug.Assert(n >= 0);
- return (int)((uint)(n - 1 + (1 << BitShiftPerInt32)) >> BitShiftPerInt32);
- }
-
- private static int GetInt32ArrayLengthFromByteLength(int n)
- {
- Debug.Assert(n >= 0);
- // Due to sign extension, we don't need to special case for n == 0, since ((n - 1) >> 2) + 1 = 0
- // This doesn't hold true for ((n - 1) / 4) + 1, which equals 1.
- return (int)((uint)(n - 1 + (1 << BitShiftForBytesPerInt32)) >> BitShiftForBytesPerInt32);
- }
-
- private static int GetByteArrayLengthFromBitLength(int n)
- {
- Debug.Assert(n >= 0);
- // Due to sign extension, we don't need to special case for n == 0, since ((n - 1) >> 3) + 1 = 0
- // This doesn't hold true for ((n - 1) / 8) + 1, which equals 1.
- return (int)((uint)(n - 1 + (1 << BitShiftPerByte)) >> BitShiftPerByte);
- }
-
- private static int Div32Rem(int number, out int remainder)
- {
- uint quotient = (uint)number / 32;
- remainder = number & (32 - 1); // equivalent to number % 32, since 32 is a power of 2
- return (int)quotient;
- }
-
- private static int Div4Rem(int number, out int remainder)
- {
- uint quotient = (uint)number / 4;
- remainder = number & (4 - 1); // equivalent to number % 4, since 4 is a power of 2
- return (int)quotient;
- }
-
- private static void ThrowArgumentOutOfRangeException(int index)
- {
- throw new ArgumentOutOfRangeException(nameof(index), index, SR.ArgumentOutOfRange_IndexMustBeLess);
- }
-
- private sealed class BitArrayEnumeratorSimple : IEnumerator, ICloneable
- {
- private readonly BitArray _bitArray;
- private int _index;
- private readonly int _version;
- private bool _currentElement;
-
- internal BitArrayEnumeratorSimple(BitArray bitArray)
- {
- _bitArray = bitArray;
- _index = -1;
- _version = bitArray._version;
- }
-
- public object Clone() => MemberwiseClone();
-
- public bool MoveNext()
- {
- if (_version != _bitArray._version)
- {
- throw new InvalidOperationException(SR.InvalidOperation_EnumFailedVersion);
- }
-
- if (_index < (_bitArray.m_length - 1))
- {
- _index++;
- _currentElement = _bitArray.Get(_index);
- return true;
- }
- else
- {
- _index = _bitArray.m_length;
- }
-
- return false;
- }
-
- public object Current
- {
- get
- {
- if ((uint)_index >= (uint)_bitArray.m_length)
- {
- throw GetInvalidOperationException(_index);
- }
-
- return _currentElement;
- }
- }
-
- public void Reset()
- {
- if (_version != _bitArray._version)
- {
- throw new InvalidOperationException(SR.InvalidOperation_EnumFailedVersion);
- }
-
- _index = -1;
- }
-
- private InvalidOperationException GetInvalidOperationException(int index)
- {
- if (index == -1)
- {
- return new InvalidOperationException(SR.InvalidOperation_EnumNotStarted);
- }
- else
- {
- Debug.Assert(index >= _bitArray.m_length);
- return new InvalidOperationException(SR.InvalidOperation_EnumEnded);
- }
- }
- }
- }
-}
diff --git a/src/libraries/System.Collections/tests/BitArray/BitArray_GetSetTests.cs b/src/libraries/System.Collections/tests/BitArray/BitArray_GetSetTests.cs
index 912164e4efc567..6a985ff182a70b 100644
--- a/src/libraries/System.Collections/tests/BitArray/BitArray_GetSetTests.cs
+++ b/src/libraries/System.Collections/tests/BitArray/BitArray_GetSetTests.cs
@@ -3,6 +3,7 @@
using System.Collections.Generic;
using System.Linq;
+using System.Runtime.InteropServices;
using Xunit;
namespace System.Collections.Tests
@@ -328,6 +329,92 @@ public static void CopyTo(BitArray bitArray, int destinationLength, int start
}
}
+ [Fact]
+ public static void AsBytes_NullInput_ProducesEmptySpan()
+ {
+ BitArray? ba = null;
+ Span bytes = CollectionsMarshal.AsBytes(ba);
+ Assert.True(bytes.IsEmpty);
+ }
+
+ [Fact]
+ public static void AsBytes_RoundtripsCtor()
+ {
+ Random r = new();
+
+ for (int length = 0; length < 128; length++)
+ {
+ bool[] bits = new bool[length];
+ for (int i = 0; i < bits.Length; i++)
+ {
+ bits[i] = r.Next(0, 2) == 0;
+ }
+
+ BitArray ba = new(bits);
+
+ Span bytes = CollectionsMarshal.AsBytes(ba);
+ Assert.Equal((length + 7) / 8, bytes.Length);
+ for (int i = 0; i < length; i++)
+ {
+ Assert.Equal(bits[i], (bytes[i / 8] & (1 << (i % 8))) != 0);
+ }
+ for (int i = length; i < bytes.Length * 8; i++)
+ {
+ Assert.Equal(0, bytes[i / 8] & (1 << (i % 8)));
+ }
+ }
+ }
+
+ [Fact]
+ public static void AsBytes_ChangesReflectedInBitArray()
+ {
+ BitArray ba = new(8);
+ Span bytes = CollectionsMarshal.AsBytes(ba);
+ Assert.Equal(0, bytes[0]);
+
+ bytes[0] = 0b11100101;
+ Assert.True(ba[0]);
+ Assert.False(ba[1]);
+ Assert.True(ba[2]);
+ Assert.False(ba[3]);
+ Assert.False(ba[4]);
+ Assert.True(ba[5]);
+ Assert.True(ba[6]);
+ Assert.True(ba[7]);
+ }
+
+ [Fact]
+ public static void AsBytes_ExtraBitsDontAffectReadOperations()
+ {
+ BitArray ba = new(4);
+ Span bytes = CollectionsMarshal.AsBytes(ba);
+ Assert.Equal(0, bytes[0]);
+
+ bytes[0] = 0xFF;
+
+ Assert.True(ba[0]);
+ Assert.True(ba[1]);
+ Assert.True(ba[2]);
+ Assert.True(ba[3]);
+
+ Assert.True(ba.HasAllSet());
+ Assert.True(ba.HasAnySet());
+
+ ba[0] = false;
+
+ Assert.False(ba.HasAllSet());
+ Assert.True(ba.HasAnySet());
+
+ ba[1] = false;
+ ba[2] = false;
+ ba[3] = false;
+
+ Assert.False(ba.HasAllSet());
+ Assert.False(ba.HasAnySet());
+
+ Assert.Equal(0b11110000, bytes[0]);
+ }
+
[Fact]
public static void CopyToByteArray()
{
diff --git a/src/libraries/System.Private.CoreLib/src/CompatibilitySuppressions.xml b/src/libraries/System.Private.CoreLib/src/CompatibilitySuppressions.xml
index 82263c77811d86..21e9323f9e7168 100644
--- a/src/libraries/System.Private.CoreLib/src/CompatibilitySuppressions.xml
+++ b/src/libraries/System.Private.CoreLib/src/CompatibilitySuppressions.xml
@@ -25,4 +25,10 @@
CP0002
M:System.String.TrimStart(System.ReadOnlySpan{System.Char})
+
+ CP0008
+ T:System.Collections.BitArray
+ ref/net10.0/System.Private.CoreLib.dll
+ lib/net10.0/System.Private.CoreLib.dll
+
diff --git a/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx b/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx
index ea16db6230b4f8..c26261f4758f4c 100644
--- a/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx
+++ b/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx
@@ -214,6 +214,9 @@
Destination array is not long enough to copy all the items in the collection. Check array index and length.
+
+ Only supported array types for CopyTo on BitArrays are Boolean[], Int32[] and Byte[].
+
The array starting from the specified index is not long enough to read a value of the specified type.
@@ -879,6 +882,9 @@
Error parsing array spec.
+
+ The input array length must not exceed Int32.MaxValue / {0}. Otherwise BitArray.Length would exceed Int32.MaxValue.
+
Attribute names must be unique.
@@ -4364,4 +4370,4 @@
Dynamic entrypoint allocation is not supported in the current environment.
-
\ No newline at end of file
+
diff --git a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems
index cea0562929595a..c1293cc2716cf4 100644
--- a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems
+++ b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems
@@ -180,6 +180,7 @@
+
diff --git a/src/libraries/System.Private.CoreLib/src/System/Collections/BitArray.cs b/src/libraries/System.Private.CoreLib/src/System/Collections/BitArray.cs
new file mode 100644
index 00000000000000..66fe6f6c37eff6
--- /dev/null
+++ b/src/libraries/System.Private.CoreLib/src/System/Collections/BitArray.cs
@@ -0,0 +1,1052 @@
+// 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.Numerics;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using System.Runtime.Intrinsics;
+using System.Runtime.Intrinsics.Arm;
+using System.Runtime.Intrinsics.X86;
+using System.Runtime.Serialization;
+
+namespace System.Collections
+{
+ ///
+ /// Manages a compact array of bit values, which are represented as , where
+ /// indicates that the bit is on (1) and indicates
+ /// the bit is off (0).
+ ///
+ [Serializable]
+ [TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
+ public sealed class BitArray : ICollection, ICloneable,
+ ISerializable // introduced in .NET 10 for compat with existing serialized assets, not exposed in the ref assembly
+ {
+ /// sizeof(int) * 8
+ private const int BitsPerInt32 = 32;
+ /// sizeof(byte) * 8
+ private const int BitsPerByte = 8;
+
+ /// The array of bytes used to store bits.
+ ///
+ /// The array is allocated to hold enough bytes to store the specified number of bits, rounded up to the nearest multiple
+ /// of sizeof(int). The last four bytes that might contain valid bits are always kept in a state where the unused bits
+ /// are cleared, such that AsBytes and CopyTo operations will not show any set bits that are not actually set.
+ ///
+ internal byte[] _array;
+
+ /// The number of bits in the array.
+ private int _bitLength;
+
+ /// Version number incremented on mutation, used to invalidate enumerators.
+ private int _version;
+
+ ///
+ /// Initializes a new instance of the class that can hold the specified
+ /// number of bit values, which are initially set to false.
+ ///
+ /// The number of bit values in the new .
+ /// is less than zero.
+ public BitArray(int length)
+ : this(length, false)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class that can hold the specified number of
+ /// bit values, which are initially set to the specified value.
+ ///
+ /// The number of bit values in the new .
+ /// The Boolean value to assign to each bit.
+ /// is less than zero.
+ public BitArray(int length, bool defaultValue)
+ {
+ ArgumentOutOfRangeException.ThrowIfNegative(length);
+
+ _array = AllocateByteArray(length);
+ _bitLength = length;
+
+ if (defaultValue)
+ {
+ Array.Fill(_array, (byte)0xFF);
+ ClearHighExtraBits();
+ }
+ }
+
+ /// Deserializes BitArray in a way that's compatible with the original .NET Framework implementation.
+ private BitArray(SerializationInfo info, StreamingContext context)
+ {
+ ArgumentNullException.ThrowIfNull(info);
+
+ var array = (int[]?)info.GetValue("m_array", typeof(int[]));
+ _bitLength = info.GetInt32("m_length");
+ _version = info.GetInt32("_version");
+
+ if (array is null || (uint)_bitLength > checked((uint)array.Length * BitsPerInt32))
+ {
+ throw new SerializationException(SR.Serialization_InvalidData);
+ }
+
+ _array = AllocateByteArray(_bitLength);
+ if (BitConverter.IsLittleEndian)
+ {
+ MemoryMarshal.AsBytes(array).CopyTo(_array);
+ }
+ else
+ {
+ BinaryPrimitives.ReverseEndianness(array, MemoryMarshal.Cast((Span)_array));
+ }
+ }
+
+ /// Generates serialization data for the BitArray in a way that's compatible with the original .NET Framework implementation.
+ void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context)
+ {
+ ArgumentNullException.ThrowIfNull(info);
+
+ var m_array = new int[GetInt32ArrayLengthFromBitLength(_bitLength)];
+ CopyTo(m_array, 0);
+
+ info.AddValue("m_array", m_array);
+ info.AddValue("m_length", _bitLength);
+ info.AddValue("_version", _version);
+ }
+
+ private void ClearHighExtraBits()
+ {
+ (uint index, uint extraBits) = Math.DivRem((uint)_bitLength, BitsPerInt32);
+ if (extraBits != 0)
+ {
+ MemoryMarshal.Cast((Span)_array)[(int)index] &= ReverseIfBE((1 << (int)extraBits) - 1);
+ }
+ }
+
+ ///
+ /// Initializes a new instance of the class that contains bit values copied
+ /// from the specified array of bytes.
+ ///
+ /// An array of bytes containing the values to copy, where each byte represents eight consecutive bits.
+ /// is null.
+ /// The length of in bits is greater than .
+ public BitArray(byte[] bytes)
+ {
+ ArgumentNullException.ThrowIfNull(bytes);
+ if (bytes.Length > int.MaxValue / BitsPerByte)
+ {
+ throw new ArgumentException(SR.Format(SR.Argument_ArrayTooLarge, BitsPerByte), nameof(bytes));
+ }
+
+ _bitLength = bytes.Length * BitsPerByte;
+ _array = AllocateByteArray(_bitLength);
+
+ Array.Copy(bytes, _array, bytes.Length);
+ }
+
+ ///
+ /// Initializes a new instance of the class that contains bit values
+ /// copied from the specified array of Booleans.
+ ///
+ /// An array of Booleans to copy.
+ /// is null.
+ public BitArray(bool[] values)
+ {
+ ArgumentNullException.ThrowIfNull(values);
+
+ _array = AllocateByteArray(values.Length);
+ _bitLength = values.Length;
+
+ uint i = 0;
+
+ if (!BitConverter.IsLittleEndian || values.Length < Vector256.Count)
+ {
+ goto Remainder;
+ }
+
+ // Comparing with 1s would get rid of the final negation, however this would not work for some CLR bools
+ // (true for any non-zero values, false for 0) - any values between 2-255 will be interpreted as false.
+ // Instead, We compare with zeroes (== false) then negate the result to ensure compatibility.
+
+ ref byte arrayRef = ref MemoryMarshal.GetArrayDataReference(_array);
+ ref byte value = ref Unsafe.As(ref MemoryMarshal.GetArrayDataReference(values));
+ if (Vector512.IsHardwareAccelerated)
+ {
+ for (; i <= (uint)values.Length - Vector512.Count; i += (uint)Vector512.Count)
+ {
+ Vector512 vector = Vector512.LoadUnsafe(ref value, i);
+ Vector512 isFalse = Vector512.Equals(vector, Vector512.Zero);
+
+ ulong result = isFalse.ExtractMostSignificantBits();
+ Unsafe.WriteUnaligned(ref Unsafe.Add(ref arrayRef, sizeof(ulong) * (i / 64u)), ~result);
+ }
+ }
+ else if (Vector256.IsHardwareAccelerated)
+ {
+ for (; i <= (uint)values.Length - Vector256.Count; i += (uint)Vector256.Count)
+ {
+ Vector256 vector = Vector256.LoadUnsafe(ref value, i);
+ Vector256 isFalse = Vector256.Equals(vector, Vector256.Zero);
+
+ uint result = isFalse.ExtractMostSignificantBits();
+ Unsafe.WriteUnaligned(ref Unsafe.Add(ref arrayRef, sizeof(uint) * (i / 32u)), ~result);
+ }
+ }
+ else if (Vector128.IsHardwareAccelerated)
+ {
+ for (; i <= (uint)values.Length - Vector128.Count * 2u; i += (uint)Vector128.Count * 2u)
+ {
+ Vector128 lowerVector = Vector128.LoadUnsafe(ref value, i);
+ Vector128 lowerIsFalse = Vector128.Equals(lowerVector, Vector128.Zero);
+ uint lowerResult = lowerIsFalse.ExtractMostSignificantBits();
+
+ Vector128 upperVector = Vector128.LoadUnsafe(ref value, i + (uint)Vector128.Count);
+ Vector128 upperIsFalse = Vector128.Equals(upperVector, Vector128.Zero);
+ uint upperResult = upperIsFalse.ExtractMostSignificantBits();
+
+ Unsafe.WriteUnaligned(
+ ref Unsafe.Add(ref arrayRef, sizeof(uint) * (i / 32u)),
+ ~((upperResult << 16) | lowerResult));
+ }
+ }
+
+ Remainder:
+ for (; i < (uint)values.Length; i++)
+ {
+ if (values[i])
+ {
+ (uint byteIndex, uint bitOffset) = Math.DivRem(i, BitsPerByte);
+ _array[byteIndex] |= (byte)(1 << (int)bitOffset);
+ }
+ }
+ }
+
+ ///
+ /// Initializes a new instance of the class that contains bit values
+ /// copied from the specified array of 32-bit integers.
+ ///
+ /// An array of integers containing the values to copy, where each integer represents 32 consecutive bits.
+ /// is null.
+ /// The length of in bits is greater than .
+ ///
+ /// The number in the first array element represents bits 0 through 31, the second number in the array represents
+ /// bits 32 through 63, and so on. The Least Significant Bit of each integer represents the lowest index value:
+ /// "[0] & 1" represents bit 0, "[0] & 2" represents bit 1,
+ /// "[0] & 4" represents bit 2, and so on.
+ ///
+ public BitArray(int[] values)
+ {
+ ArgumentNullException.ThrowIfNull(values);
+ if (values.Length > int.MaxValue / BitsPerInt32)
+ {
+ throw new ArgumentException(SR.Format(SR.Argument_ArrayTooLarge, BitsPerInt32), nameof(values));
+ }
+
+ _bitLength = values.Length * BitsPerInt32;
+ _array = AllocateByteArray(_bitLength);
+
+ if (BitConverter.IsLittleEndian)
+ {
+ MemoryMarshal.AsBytes(values).CopyTo(_array);
+ }
+ else
+ {
+ BinaryPrimitives.ReverseEndianness(values, MemoryMarshal.Cast((Span)_array));
+ }
+ }
+
+ ///
+ /// Initializes a new instance of the class that contains bit values copied from the specified BitArray.
+ ///
+ /// The to copy.
+ /// is null.
+ public BitArray(BitArray bits)
+ {
+ ArgumentNullException.ThrowIfNull(bits);
+
+ _bitLength = bits._bitLength;
+ _array = AllocateByteArray(_bitLength);
+
+ Array.Copy(bits._array, _array, _array.Length);
+ }
+
+ ///
+ /// Gets or sets the value of the bit at a specific position in the .
+ ///
+ /// The zero-based index of the value to get or set.
+ /// The value of the bit at position .
+ /// The value of the bit at position .
+ /// is less than zero.
+ /// is greater than or equal to .
+ public bool this[int index]
+ {
+ get => Get(index);
+ set => Set(index, value);
+ }
+
+ ///
+ /// Gets the value of the bit at a specific position in the .
+ ///
+ /// The zero-based index of the value to get.
+ /// The value of the bit at position .
+ /// is less than zero.
+ /// is greater than or equal to .
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public bool Get(int index)
+ {
+ if ((uint)index >= (uint)_bitLength)
+ {
+ ThrowArgumentOutOfRangeException(index);
+ }
+
+ (uint byteIndex, uint bitOffset) = Math.DivRem((uint)index, BitsPerByte);
+ return ((_array[byteIndex]) & (1 << (int)bitOffset)) != 0;
+ }
+
+ ///
+ /// Sets the value of the bit at a specific position in the .
+ ///
+ /// The zero-based index of the value to get.
+ /// The Boolean value to assign to the bit.
+ /// is less than zero.
+ /// is greater than or equal to .
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void Set(int index, bool value)
+ {
+ if ((uint)index >= (uint)_bitLength)
+ {
+ ThrowArgumentOutOfRangeException(index);
+ }
+
+ (uint byteIndex, uint bitOffset) = Math.DivRem((uint)index, BitsPerByte);
+
+ ref byte segment = ref _array[byteIndex];
+ byte bitMask = (byte)(1 << (int)bitOffset);
+ if (value)
+ {
+ segment |= bitMask;
+ }
+ else
+ {
+ segment &= (byte)~bitMask;
+ }
+
+ _version++;
+ }
+
+ ///
+ /// Sets all bits in the to the specified value.
+ ///
+ /// The Boolean value to assign to all bits.
+ public void SetAll(bool value)
+ {
+ if (value)
+ {
+ _array.AsSpan(0, GetByteArrayLengthFromBitLength(_bitLength)).Fill(0xFF);
+ ClearHighExtraBits();
+ }
+ else
+ {
+ _array.AsSpan(0, GetByteArrayLengthFromBitLength(_bitLength)).Clear();
+ }
+
+ _version++;
+ }
+
+ ///
+ /// Performs the bitwise AND operation between the elements of the current object and the
+ /// corresponding elements in the specified array. The current object will be modified to
+ /// store the result of the bitwise AND operation.
+ ///
+ /// The array with which to perform the bitwise AND operation.
+ /// An array containing the result of the bitwise AND operation, which is a reference to the current object.
+ /// is null.
+ /// and the current do not have the same number of elements.
+ public BitArray And(BitArray value) => Invoke(value);
+
+ ///
+ /// Performs the bitwise OR operation between the elements of the current object and the
+ /// corresponding elements in the specified array. The current object will be modified to
+ /// store the result of the bitwise OR operation.
+ ///
+ /// The array with which to perform the bitwise OR operation.
+ /// An array containing the result of the bitwise OR operation, which is a reference to the current object.
+ /// is null.
+ /// and the current do not have the same number of elements.
+ public BitArray Or(BitArray value) => Invoke(value);
+
+ ///
+ /// Performs the bitwise XOR operation between the elements of the current object and the
+ /// corresponding elements in the specified array. The current object will be modified to
+ /// store the result of the bitwise XOR operation.
+ ///
+ /// The array with which to perform the bitwise XOR operation.
+ /// An array containing the result of the bitwise XOR operation, which is a reference to the current object.
+ /// is null.
+ /// and the current do not have the same number of elements.
+ public BitArray Xor(BitArray value) => Invoke(value);
+
+ ///
+ /// Inverts all the bit values in the current , so that elements set to true are changed to false,
+ /// and elements set to false are changed to true.
+ ///
+ /// The current instance with inverted bit values.
+ public BitArray Not()
+ {
+ Invoke(this); // argument is ignored
+ ClearHighExtraBits(); // applying ~ to last Int32 may set extra bits we're trying to keep clear
+ return this;
+ }
+
+ /// Provides the implementation for , , etc.
+ private BitArray Invoke(BitArray value) where TBinaryOp : struct, IBinaryOp
+ {
+ ArgumentNullException.ThrowIfNull(value);
+
+ // This method uses unsafe code to manipulate data in the BitArrays. To avoid issues with
+ // buggy code concurrently mutating these instances in a way that could cause memory corruption,
+ // we snapshot the arrays from both and then operate only on those snapshots, while also validating
+ // that the count we iterate to is within the bounds of both arrays. We don't care about such code
+ // corrupting the BitArray data in a way that produces incorrect answers, since BitArray is not meant
+ // to be thread-safe; we only care about avoiding buffer overruns.
+ byte[] thisArray = _array;
+ byte[] valueArray = value._array;
+ int count = GetByteArrayLengthFromBitLength(Length);
+
+ if (Length != value.Length ||
+ (uint)count > (uint)thisArray.Length ||
+ (uint)count > (uint)valueArray.Length)
+ {
+ throw new ArgumentException(SR.Arg_ArrayLengthsDiffer);
+ }
+
+ int i = 0;
+
+ if (Vector512.IsHardwareAccelerated)
+ {
+ i = Apply>(count, thisArray, valueArray);
+ }
+ else if (Vector256.IsHardwareAccelerated)
+ {
+ i = Apply>(count, thisArray, valueArray);
+ }
+ else if (Vector128.IsHardwareAccelerated)
+ {
+ i = Apply>(count, thisArray, valueArray);
+ }
+
+ // Process remaining.
+ if (i != count)
+ {
+ int roundedBytesRemaining = RoundUpToMultipleSizeOfInt32(count - i);
+ Span thisRemaining = MemoryMarshal.Cast(((Span)thisArray).Slice(i, roundedBytesRemaining));
+ Span valueRemaining = MemoryMarshal.Cast(((Span)valueArray).Slice(i, roundedBytesRemaining));
+ for (i = 0; i < thisRemaining.Length; i++)
+ {
+ thisRemaining[i] = TBinaryOp.Invoke(thisRemaining[i], valueRemaining[i]);
+ }
+ }
+
+ _version++;
+ return this;
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ static int Apply(int count, byte[] thisArray, byte[] valueArray)
+ where TVector : ISimdVector
+ {
+ ref byte left = ref MemoryMarshal.GetArrayDataReference(thisArray);
+ ref byte right = ref MemoryMarshal.GetArrayDataReference(valueArray);
+
+ int i;
+
+ for (i = 0; i <= count - TVector.ElementCount; i += TVector.ElementCount)
+ {
+ TVector result = TBinaryOp.Invoke(TVector.LoadUnsafe(ref left, (uint)i), TVector.LoadUnsafe(ref right, (uint)i));
+ result.StoreUnsafe(ref left, (uint)i);
+ }
+
+ return i;
+ }
+ }
+
+ private struct AndBinaryOp : IBinaryOp
+ {
+ public static int Invoke(int value1, int value2) => value1 & value2;
+ public static TVector Invoke(TVector value1, TVector value2) where TVector : ISimdVector => value1 & value2;
+ }
+
+ private struct OrBinaryOp : IBinaryOp
+ {
+ public static int Invoke(int value1, int value2) => value1 | value2;
+ public static TVector Invoke(TVector value1, TVector value2) where TVector : ISimdVector => value1 | value2;
+ }
+
+ private struct XorBinaryOp : IBinaryOp
+ {
+ public static int Invoke(int value1, int value2) => value1 ^ value2;
+ public static TVector Invoke(TVector value1, TVector value2) where TVector : ISimdVector => value1 ^ value2;
+ }
+
+ private struct NotBinaryOp : IBinaryOp // not isn't binary, so second argument is just ignored
+ {
+ public static int Invoke(int value1, int _) => ~value1;
+ public static TVector Invoke(TVector value1, TVector value2) where TVector : ISimdVector => ~value1;
+ }
+
+ private interface IBinaryOp
+ {
+ static abstract int Invoke(int value1, int value2);
+ static abstract TVector Invoke(TVector value1, TVector value2) where TVector : ISimdVector;
+ }
+
+ ///
+ /// Shifts all the bit values of the current to the right on bits.
+ ///
+ /// The number of shifts to make for each bit.
+ /// The current .
+ /// is less than zero.
+ public BitArray RightShift(int count)
+ {
+ if (count <= 0)
+ {
+ ArgumentOutOfRangeException.ThrowIfNegative(count);
+
+ _version++;
+ return this;
+ }
+
+ Span intSpan = MemoryMarshal.Cast((Span)_array);
+
+ 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);
+ 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;
+ }
+ else
+ {
+ int lastIndex = ints - 1;
+
+ while (fromIndex < lastIndex)
+ {
+ uint right = (uint)ReverseIfBE(intSpan[fromIndex]) >> shiftCount;
+ int left = ReverseIfBE(intSpan[++fromIndex]) << (BitsPerInt32 - shiftCount);
+ intSpan[toIndex++] = ReverseIfBE(left | (int)right);
+ }
+
+ uint mask = uint.MaxValue >> (BitsPerInt32 - extraBits);
+ mask &= (uint)ReverseIfBE(intSpan[fromIndex]);
+ intSpan[toIndex++] = ReverseIfBE((int)(mask >> shiftCount));
+ }
+ }
+
+ intSpan.Slice(toIndex, ints - toIndex).Clear();
+ _version++;
+ return this;
+ }
+
+ ///
+ /// Shifts all the bit values of the current to the left on bits.
+ ///
+ /// The number of shifts to make for each bit.
+ /// The current .
+ /// is less than zero.
+ public BitArray LeftShift(int count)
+ {
+ if (count <= 0)
+ {
+ ArgumentOutOfRangeException.ThrowIfNegative(count);
+
+ _version++;
+ return this;
+ }
+
+ Span intSpan = MemoryMarshal.Cast((Span)_array);
+
+ int lengthToClear;
+ if (count < _bitLength)
+ {
+ int lastIndex = (int)((uint)(_bitLength - 1) / BitsPerInt32);
+
+ (lengthToClear, int shiftCount) = Math.DivRem(count, BitsPerInt32);
+
+ if (shiftCount == 0)
+ {
+ intSpan.Slice(0, lastIndex + 1 - lengthToClear).CopyTo(intSpan.Slice(lengthToClear));
+ }
+ else
+ {
+ int fromindex = lastIndex - lengthToClear;
+
+ while (fromindex > 0)
+ {
+ int left = ReverseIfBE(intSpan[fromindex]) << shiftCount;
+ uint right = (uint)ReverseIfBE(intSpan[--fromindex]) >> (BitsPerInt32 - shiftCount);
+ intSpan[lastIndex] = ReverseIfBE(left | (int)right);
+ lastIndex--;
+ }
+ intSpan[lastIndex] = ReverseIfBE(ReverseIfBE(intSpan[fromindex]) << shiftCount);
+ }
+ }
+ else
+ {
+ lengthToClear = GetInt32ArrayLengthFromBitLength(_bitLength); // Clear all
+ }
+
+ intSpan.Slice(0, lengthToClear).Clear();
+ _version++;
+ return this;
+ }
+
+ ///
+ /// Gets or sets the number of elements in the .
+ ///
+ /// The number of elements in the .
+ /// The property is set to a value that is less than zero.
+ public int Length
+ {
+ get => _bitLength;
+ set
+ {
+ ArgumentOutOfRangeException.ThrowIfNegative(value);
+
+ int newByteLength = GetAlignedByteArrayLength(value);
+ if (newByteLength > _array.Length)
+ {
+ Array.Resize(ref _array, newByteLength);
+ }
+ else
+ {
+ int currentByteLength = GetByteArrayLengthFromBitLength(_bitLength);
+ if (newByteLength > currentByteLength)
+ {
+ _array.AsSpan(currentByteLength).Clear();
+ }
+ else
+ {
+ // If we'll be shrinking by a significant amount, re-allocate to avoid wasting too much space.
+ const int ShrinkThreshold = 1024;
+ if (newByteLength < _array.Length - ShrinkThreshold)
+ {
+ Array.Resize(ref _array, newByteLength);
+ }
+ }
+ }
+
+ _bitLength = value;
+ ClearHighExtraBits();
+
+ _version++;
+ }
+ }
+
+ ///
+ public unsafe void CopyTo(Array array, int index)
+ {
+ ArgumentNullException.ThrowIfNull(array);
+ ArgumentOutOfRangeException.ThrowIfNegative(index);
+
+ if (array.Rank != 1)
+ {
+ throw new ArgumentException(SR.Arg_RankMultiDimNotSupported, nameof(array));
+ }
+
+ if (array is int[] intArray)
+ {
+ int intLength = GetInt32ArrayLengthFromBitLength(_bitLength);
+
+ if (array.Length - index < intLength)
+ {
+ throw new ArgumentException(SR.Argument_InvalidOffLen);
+ }
+
+ if (intLength > 0)
+ {
+ Span source = MemoryMarshal.Cast((Span)_array).Slice(0, intLength);
+ if (BitConverter.IsLittleEndian)
+ {
+ source.CopyTo(intArray.AsSpan(index));
+ }
+ else
+ {
+ BinaryPrimitives.ReverseEndianness(source, intArray.AsSpan(index));
+ }
+
+ uint extraBits = (uint)_bitLength % BitsPerInt32;
+ if (extraBits != 0)
+ {
+ intArray[index + intLength - 1] = ReverseIfBE(source[^1]) & ((1 << (int)extraBits) - 1);
+ }
+ }
+ }
+ else if (array is byte[] byteArray)
+ {
+ int byteLength = GetByteArrayLengthFromBitLength(_bitLength);
+
+ if ((array.Length - index) < byteLength)
+ {
+ throw new ArgumentException(SR.Argument_InvalidOffLen);
+ }
+
+ if (byteLength > 0)
+ {
+ ReadOnlySpan source = _array.AsSpan(0, byteLength);
+ source.CopyTo(byteArray.AsSpan(index));
+
+ uint extraBits = (uint)_bitLength % BitsPerByte;
+ if (extraBits != 0)
+ {
+ byteArray[index + byteLength - 1] = (byte)(source[^1] & ((1 << (int)extraBits) - 1));
+ }
+ }
+ }
+ else if (array is bool[] boolArray)
+ {
+ if (boolArray.Length - index < _bitLength)
+ {
+ throw new ArgumentException(SR.Argument_InvalidOffLen);
+ }
+
+ uint i = 0;
+
+ if (!BitConverter.IsLittleEndian || _bitLength < BitsPerInt32)
+ {
+ goto Remainder;
+ }
+
+ Span in32Span = MemoryMarshal.Cast((Span)_array);
+
+ // The mask used when shuffling a single int into Vector128/256/512.
+ // On little endian machines, the lower 8 bits of int belong in the first byte, next lower 8 in the second and so on.
+ // We place the bytes that contain the bits to its respective byte so that we can mask out only the relevant bits later.
+ Vector128 lowerShuffleMask_CopyToBoolArray = Vector128.Create(0, 0x01010101_01010101).AsByte();
+ Vector128 upperShuffleMask_CopyToBoolArray = Vector128.Create(0x02020202_02020202, 0x03030303_03030303).AsByte();
+
+ if (Avx512BW.IsSupported && (uint)_bitLength >= Vector512.Count)
+ {
+ Vector256 upperShuffleMask_CopyToBoolArray256 = Vector256.Create(0x04040404_04040404, 0x05050505_05050505,
+ 0x06060606_06060606, 0x07070707_07070707).AsByte();
+ Vector256 lowerShuffleMask_CopyToBoolArray256 = Vector256.Create(lowerShuffleMask_CopyToBoolArray, upperShuffleMask_CopyToBoolArray);
+ Vector512 shuffleMask = Vector512.Create(lowerShuffleMask_CopyToBoolArray256, upperShuffleMask_CopyToBoolArray256);
+ Vector512 bitMask = Vector512.Create(0x80402010_08040201).AsByte();
+ Vector512 ones = Vector512.Create((byte)1);
+
+ fixed (bool* destination = &boolArray[index])
+ {
+ for (; (i + Vector512.Count) <= (uint)_bitLength; i += (uint)Vector512.Count)
+ {
+ ulong bits = (ulong)(uint)in32Span[(int)(i / (uint)BitsPerInt32)] + ((ulong)in32Span[(int)(i / (uint)BitsPerInt32) + 1] << BitsPerInt32);
+ Vector512 scalar = Vector512.Create(bits);
+ Vector512 shuffled = Avx512BW.Shuffle(scalar.AsByte(), shuffleMask);
+ Vector512 extracted = Avx512F.And(shuffled, bitMask);
+
+ // The extracted bits can be anywhere between 0 and 255, so we normalise the value to either 0 or 1
+ // to ensure compatibility with "C# bool" (0 for false, 1 for true, rest undefined)
+ Vector512 normalized = Avx512BW.Min(extracted, ones);
+ Avx512F.Store((byte*)destination + i, normalized);
+ }
+ }
+ }
+ else if (Avx2.IsSupported && (uint)_bitLength >= Vector256.Count)
+ {
+ Vector256 shuffleMask = Vector256.Create(lowerShuffleMask_CopyToBoolArray, upperShuffleMask_CopyToBoolArray);
+ Vector256 bitMask = Vector256.Create(0x80402010_08040201).AsByte();
+ Vector256 ones = Vector256.Create((byte)1);
+
+ fixed (bool* destination = &boolArray[index])
+ {
+ for (; (i + Vector256.Count) <= (uint)_bitLength; i += (uint)Vector256.Count)
+ {
+ int bits = in32Span[(int)(i / (uint)BitsPerInt32)];
+ Vector256 scalar = Vector256.Create(bits);
+ Vector256 shuffled = Avx2.Shuffle(scalar.AsByte(), shuffleMask);
+ Vector256 extracted = Avx2.And(shuffled, bitMask);
+
+ // The extracted bits can be anywhere between 0 and 255, so we normalise the value to either 0 or 1
+ // to ensure compatibility with "C# bool" (0 for false, 1 for true, rest undefined)
+ Vector256 normalized = Avx2.Min(extracted, ones);
+ Avx.Store((byte*)destination + i, normalized);
+ }
+ }
+ }
+ else if (Ssse3.IsSupported && ((uint)_bitLength >= Vector128.Count * 2u))
+ {
+ Vector128 lowerShuffleMask = lowerShuffleMask_CopyToBoolArray;
+ Vector128 upperShuffleMask = upperShuffleMask_CopyToBoolArray;
+ Vector128 ones = Vector128.Create((byte)1);
+ Vector128 bitMask128 = Vector128.Create(0x80402010_08040201).AsByte();
+
+ fixed (bool* destination = &boolArray[index])
+ {
+ for (; (i + Vector128.Count * 2u) <= (uint)_bitLength; i += (uint)Vector128.Count * 2u)
+ {
+ int bits = in32Span[(int)(i / (uint)BitsPerInt32)];
+ Vector128 scalar = Vector128.CreateScalarUnsafe(bits);
+
+ Vector128 shuffledLower = Ssse3.Shuffle(scalar.AsByte(), lowerShuffleMask);
+ Vector128 extractedLower = Sse2.And(shuffledLower, bitMask128);
+ Vector128 normalizedLower = Sse2.Min(extractedLower, ones);
+ Sse2.Store((byte*)destination + i, normalizedLower);
+
+ Vector128 shuffledHigher = Ssse3.Shuffle(scalar.AsByte(), upperShuffleMask);
+ Vector128 extractedHigher = Sse2.And(shuffledHigher, bitMask128);
+ Vector128 normalizedHigher = Sse2.Min(extractedHigher, ones);
+ Sse2.Store((byte*)destination + i + Vector128.Count, normalizedHigher);
+ }
+ }
+ }
+ else if (AdvSimd.Arm64.IsSupported)
+ {
+ Vector128 ones = Vector128.Create((byte)1);
+ Vector128 bitMask128 = Vector128.Create(0x80402010_08040201).AsByte();
+
+ fixed (bool* destination = &boolArray[index])
+ {
+ for (; (i + Vector128.Count * 2u) <= (uint)_bitLength; i += (uint)Vector128.Count * 2u)
+ {
+ int bits = in32Span[(int)(i / (uint)BitsPerInt32)];
+
+ // Same logic as SSSE3 path, except we do not have Shuffle instruction.
+ // (TableVectorLookup could be an alternative - dotnet/runtime#1277)
+ // Instead we use chained ZIP1/2 instructions:
+ // (A0 is the byte containing LSB, A3 is the byte containing MSB)
+ // bits - A0 A1 A2 A3
+ // v1 = Vector128.Create - A0 A1 A2 A3 A0 A1 A2 A3 A0 A1 A2 A3 A0 A1 A2 A3
+ // v2 = ZipLow(v1, v1) - A0 A0 A1 A1 A2 A2 A3 A3 A0 A0 A1 A1 A2 A2 A3 A3
+ // v3 = ZipLow(v2, v2) - A0 A0 A0 A0 A1 A1 A1 A1 A2 A2 A2 A2 A3 A3 A3 A3
+ // shuffledLower = ZipLow(v3, v3) - A0 A0 A0 A0 A0 A0 A0 A0 A1 A1 A1 A1 A1 A1 A1 A1
+ // shuffledHigher = ZipHigh(v3, v3) - A2 A2 A2 A2 A2 A2 A2 A2 A3 A3 A3 A3 A3 A3 A3 A3
+
+ Vector128 vector = Vector128.Create(bits).AsByte();
+ vector = AdvSimd.Arm64.ZipLow(vector, vector);
+ vector = AdvSimd.Arm64.ZipLow(vector, vector);
+
+ Vector128 shuffledLower = AdvSimd.Arm64.ZipLow(vector, vector);
+ Vector128 extractedLower = AdvSimd.And(shuffledLower, bitMask128);
+ Vector128 normalizedLower = AdvSimd.Min(extractedLower, ones);
+
+ Vector128 shuffledHigher = AdvSimd.Arm64.ZipHigh(vector, vector);
+ Vector128 extractedHigher = AdvSimd.And(shuffledHigher, bitMask128);
+ Vector128 normalizedHigher = AdvSimd.Min(extractedHigher, ones);
+
+ AdvSimd.Arm64.StorePair((byte*)destination + i, normalizedLower, normalizedHigher);
+ }
+ }
+ }
+
+ Remainder:
+ for (; i < (uint)_bitLength; i++)
+ {
+ (uint byteIndex, uint extraBits) = Math.DivRem(i, BitsPerByte);
+ boolArray[(uint)index + i] = (_array[byteIndex] & (1 << (int)extraBits)) != 0;
+ }
+ }
+ else
+ {
+ throw new ArgumentException(SR.Arg_BitArrayTypeUnsupported, nameof(array));
+ }
+ }
+
+ ///
+ /// Determines whether all bits in the