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 are set to true. + /// + /// true if every bit in the is set to true, or if is empty; otherwise, false. + public bool HasAllSet() + { + uint extraBits = (uint)_bitLength % BitsPerByte; + int byteCount = GetByteArrayLengthFromBitLength(_bitLength); + if (extraBits != 0) + { + byteCount--; + } + + if (_array.AsSpan(0, byteCount).ContainsAnyExcept((byte)0xFF)) + { + return false; + } + + if (extraBits == 0) + { + return true; + } + + byte mask = (byte)((1 << (int)extraBits) - 1); + return (_array[byteCount] & 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() + { + uint extraBits = (uint)_bitLength % BitsPerByte; + int byteCount = GetByteArrayLengthFromBitLength(_bitLength); + if (extraBits != 0) + { + byteCount--; + } + + if (_array.AsSpan(0, byteCount).ContainsAnyExcept((byte)0)) + { + return true; + } + + if (extraBits == 0) + { + return false; + } + + byte mask = (byte)((1 << (int)extraBits) - 1); + return (_array[byteCount] & mask) != 0; + } + + /// Gets the number of elements contained in the . + public int Count => _bitLength; + + /// Gets an object that can be used to synchronize access to the . + public object SyncRoot => this; + + /// Gets a value indicating whether access to the is synchronized (thread safe). + public bool IsSynchronized => false; + + /// Gets a value indicating whether the is read-only. + public bool IsReadOnly => false; + + /// Creates a shallow copy of the . + public object Clone() => new BitArray(this); + + /// Returns an enumerator that iterates through the . + /// An IEnumerator for the entire . + public IEnumerator GetEnumerator() => new BitArrayEnumeratorSimple(this); + + /// Determines the number of s required to store bits. + private static int GetInt32ArrayLengthFromBitLength(int bitLength) + { + Debug.Assert(bitLength >= 0); + return (int)(((uint)bitLength + 31u) >> 5); + } + + /// Determines the number of s required to store bits. + internal static int GetByteArrayLengthFromBitLength(int bitLength) + { + Debug.Assert(bitLength >= 0); + return (int)(((uint)bitLength + 7u) >> 3); + } + + /// Rounds up to a multiple of sizeof(int). + private static int RoundUpToMultipleSizeOfInt32(int value) => + (value + (sizeof(int) - 1)) & ~(sizeof(int) - 1); + + private static int GetAlignedByteArrayLength(int bitLength) => + // Always allocate in groups of sizeof(int) bytes so that we can use MemoryMarshal.Cast + // to manipulate as ints when desired. + RoundUpToMultipleSizeOfInt32(GetByteArrayLengthFromBitLength(bitLength)); + + /// Allocates a new byte array of the specified bit length, rounded up to the nearest multiple of sizeof(int). + private static byte[] AllocateByteArray(int bitLength) + { + int byteLength = GetAlignedByteArrayLength(bitLength); + Debug.Assert(byteLength >= 0, "byteLength should be non-negative."); + Debug.Assert(byteLength % sizeof(int) == 0, "byteLength should be a multiple of sizeof(int)."); + return bitLength != 0 ? new byte[byteLength] : []; + } + + /// Nop on little endian, reverses the endianness of on big endian. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static int ReverseIfBE(int value) => + BitConverter.IsLittleEndian ? value : BinaryPrimitives.ReverseEndianness(value); + + private static void ThrowArgumentOutOfRangeException(int index) => + throw new ArgumentOutOfRangeException(nameof(index), index, SR.ArgumentOutOfRange_IndexMustBeLess); + + private sealed class BitArrayEnumeratorSimple : IEnumerator, ICloneable + { + private static readonly object s_boxedTrue = true; + private static readonly object s_boxedFalse = false; + + private readonly BitArray _bitArray; + private readonly int _version; + private int _index; + private object _currentElement = s_boxedFalse; + + 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._bitLength - 1)) + { + _index++; + _currentElement = _bitArray.Get(_index) ? s_boxedTrue : s_boxedFalse; + return true; + } + + _index = _bitArray._bitLength; + return false; + } + + public object Current + { + get + { + if ((uint)_index >= (uint)_bitArray._bitLength) + { + 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._bitLength); + return new InvalidOperationException(SR.InvalidOperation_EnumEnded); + } + } + } + } +} diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/CollectionsMarshal.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/CollectionsMarshal.cs index 9c372574ed0dec..e52270a905fc43 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/CollectionsMarshal.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/CollectionsMarshal.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Runtime.CompilerServices; @@ -41,6 +42,28 @@ public static Span AsSpan(List? list) return span; } + /// + /// Get a view over a 's data. + /// + /// The whose backing storage should be viewed. + /// + /// + /// The may have more capacity than is required to store the number of bits represented by + /// . The returned span's will be the smallest number + /// of bytes capable of representing that length. If the 's length is not evenly divisible by + /// 8, the last byte of the span may contain extraneous bits that do not represent elements in the . + /// These may be ignored. + /// + /// + /// The length of the should not be changed while the resulting + /// is in use. After such a change, the span may no longer refer to the 's backing storage. + /// + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Span AsBytes(BitArray? array) => + array is null ? default : + array._array.AsSpan(0, BitArray.GetByteArrayLengthFromBitLength(array.Length)); + /// /// Gets either a ref to a in the or a ref null if it does not exist in the . /// diff --git a/src/libraries/System.Runtime.InteropServices/ref/System.Runtime.InteropServices.cs b/src/libraries/System.Runtime.InteropServices/ref/System.Runtime.InteropServices.cs index e00a6de33c437f..91534aa6f11c5e 100644 --- a/src/libraries/System.Runtime.InteropServices/ref/System.Runtime.InteropServices.cs +++ b/src/libraries/System.Runtime.InteropServices/ref/System.Runtime.InteropServices.cs @@ -679,6 +679,7 @@ public CoClassAttribute(System.Type coClass) { } } public static partial class CollectionsMarshal { + public static System.Span AsBytes(System.Collections.BitArray? array) { throw null; } public static System.Span AsSpan(System.Collections.Generic.List? list) { throw null; } public static ref TValue GetValueRefOrNullRef(System.Collections.Generic.Dictionary dictionary, TKey key) where TKey : notnull { throw null; } public static ref TValue GetValueRefOrNullRef(System.Collections.Generic.Dictionary.AlternateLookup dictionary, TAlternateKey key) where TKey : notnull where TAlternateKey : notnull, allows ref struct { throw null; }