Skip to content
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
159 changes: 115 additions & 44 deletions src/libraries/System.Private.CoreLib/src/System/Collections/BitArray.cs
Original file line number Diff line number Diff line change
Expand Up @@ -142,8 +142,15 @@ private void ClearHighExtraBits()
public BitArray(byte[] bytes)
Comment thread
joshuajyue marked this conversation as resolved.
{
ArgumentNullException.ThrowIfNull(bytes);
if (bytes.Length > int.MaxValue / BitsPerByte)
{
throw new ArgumentException(SR.Format(SR.Argument_ArrayTooLarge, BitsPerByte), nameof(bytes));
}

_array = CreateArray(bytes, out _bitLength);
_bitLength = bytes.Length * BitsPerByte;
_array = AllocateByteArray(_bitLength);

Array.Copy(bytes, _array, bytes.Length);
}

/// <summary>
Expand All @@ -161,22 +168,16 @@ public BitArray(byte[] bytes)
/// This constructor is an <c>O(n)</c> operation, where <c>n</c> is the number of elements in <paramref name="bytes"/>.
/// </remarks>
public BitArray(ReadOnlySpan<byte> bytes)
{
_array = CreateArray(bytes, out _bitLength);
}

private static byte[] CreateArray(ReadOnlySpan<byte> bytes, out int bitLength)
{
if (bytes.Length > int.MaxValue / BitsPerByte)
{
throw new ArgumentException(SR.Format(SR.Argument_ArrayTooLarge, BitsPerByte), nameof(bytes));
}

bitLength = bytes.Length * BitsPerByte;
byte[] array = AllocateByteArray(bitLength);
_bitLength = bytes.Length * BitsPerByte;
_array = AllocateByteArray(_bitLength);

bytes.CopyTo(array);
return array;
bytes.CopyTo(_array);
}

/// <summary>
Expand All @@ -192,7 +193,77 @@ public BitArray(bool[] values)
{
ArgumentNullException.ThrowIfNull(values);

_array = CreateArray(values, out _bitLength);
_array = AllocateByteArray(values.Length);
_bitLength = values.Length;

uint i = 0;

if (!BitConverter.IsLittleEndian || values.Length < Vector256<byte>.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);
ReadOnlySpan<byte> valuesAsBytes = MemoryMarshal.AsBytes(values.AsSpan());
if (Vector512.IsHardwareAccelerated)
{
while (valuesAsBytes.Length >= Vector512<byte>.Count)
{
Vector512<byte> vector = Vector512.Create(valuesAsBytes);
Vector512<byte> isFalse = Vector512.Equals(vector, Vector512<byte>.Zero);

ulong result = isFalse.ExtractMostSignificantBits();
Unsafe.WriteUnaligned(ref Unsafe.Add(ref arrayRef, sizeof(ulong) * (i / 64u)), ~result);
i += (uint)Vector512<byte>.Count;
valuesAsBytes = valuesAsBytes.Slice(Vector512<byte>.Count);
}
}
else if (Vector256.IsHardwareAccelerated)
{
while (valuesAsBytes.Length >= Vector256<byte>.Count)
{
Vector256<byte> vector = Vector256.Create(valuesAsBytes);
Vector256<byte> isFalse = Vector256.Equals(vector, Vector256<byte>.Zero);

uint result = isFalse.ExtractMostSignificantBits();
Unsafe.WriteUnaligned(ref Unsafe.Add(ref arrayRef, sizeof(uint) * (i / 32u)), ~result);
i += (uint)Vector256<byte>.Count;
valuesAsBytes = valuesAsBytes.Slice(Vector256<byte>.Count);
}
}
else if (Vector128.IsHardwareAccelerated)
{
while (valuesAsBytes.Length >= Vector128<byte>.Count * 2)
{
Vector128<byte> lowerVector = Vector128.Create(valuesAsBytes);
Vector128<byte> lowerIsFalse = Vector128.Equals(lowerVector, Vector128<byte>.Zero);
uint lowerResult = lowerIsFalse.ExtractMostSignificantBits();

Vector128<byte> upperVector = Vector128.Create(valuesAsBytes.Slice(Vector128<byte>.Count));
Vector128<byte> upperIsFalse = Vector128.Equals(upperVector, Vector128<byte>.Zero);
uint upperResult = upperIsFalse.ExtractMostSignificantBits();

Unsafe.WriteUnaligned(
ref Unsafe.Add(ref arrayRef, sizeof(uint) * (i / 32u)),
~((upperResult << 16) | lowerResult));
i += (uint)Vector128<byte>.Count * 2u;
valuesAsBytes = valuesAsBytes.Slice(Vector128<byte>.Count * 2);
}
}

Remainder:
for (; i < (uint)values.Length; i++)
{
if (values[i])
{
(uint byteIndex, uint bitOffset) = Math.DivRem(i, BitsPerByte);
_array[byteIndex] |= (byte)(1 << (int)bitOffset);
}
}
}

/// <summary>
Expand All @@ -205,15 +276,10 @@ public BitArray(bool[] values)
/// </remarks>
public BitArray(ReadOnlySpan<bool> values)
{
_array = CreateArray(values, out _bitLength);
}

private static byte[] CreateArray(ReadOnlySpan<bool> values, out int bitLength)
{
bitLength = values.Length;
byte[] array = AllocateByteArray(bitLength);
_array = AllocateByteArray(values.Length);
_bitLength = values.Length;

uint i = 0;
int i = 0;

if (!BitConverter.IsLittleEndian || values.Length < Vector256<byte>.Count)
{
Expand All @@ -224,7 +290,7 @@ private static byte[] CreateArray(ReadOnlySpan<bool> values, out int bitLength)
// (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 arrayRef = ref MemoryMarshal.GetArrayDataReference(_array);
ReadOnlySpan<byte> valuesAsBytes = MemoryMarshal.AsBytes(values);
if (Vector512.IsHardwareAccelerated)
{
Expand All @@ -234,8 +300,8 @@ private static byte[] CreateArray(ReadOnlySpan<bool> values, out int bitLength)
Vector512<byte> isFalse = Vector512.Equals(vector, Vector512<byte>.Zero);

ulong result = isFalse.ExtractMostSignificantBits();
Unsafe.WriteUnaligned(ref Unsafe.Add(ref arrayRef, sizeof(ulong) * (i / 64u)), ~result);
i += (uint)Vector512<byte>.Count;
Unsafe.WriteUnaligned(ref Unsafe.Add(ref arrayRef, sizeof(ulong) * (i / 64)), ~result);
i += Vector512<byte>.Count;
valuesAsBytes = valuesAsBytes.Slice(Vector512<byte>.Count);
}
}
Expand All @@ -247,8 +313,8 @@ private static byte[] CreateArray(ReadOnlySpan<bool> values, out int bitLength)
Vector256<byte> isFalse = Vector256.Equals(vector, Vector256<byte>.Zero);

uint result = isFalse.ExtractMostSignificantBits();
Unsafe.WriteUnaligned(ref Unsafe.Add(ref arrayRef, sizeof(uint) * (i / 32u)), ~result);
i += (uint)Vector256<byte>.Count;
Unsafe.WriteUnaligned(ref Unsafe.Add(ref arrayRef, sizeof(uint) * (i / 32)), ~result);
i += Vector256<byte>.Count;
valuesAsBytes = valuesAsBytes.Slice(Vector256<byte>.Count);
}
}
Expand All @@ -265,24 +331,22 @@ private static byte[] CreateArray(ReadOnlySpan<bool> values, out int bitLength)
uint upperResult = upperIsFalse.ExtractMostSignificantBits();

Unsafe.WriteUnaligned(
ref Unsafe.Add(ref arrayRef, sizeof(uint) * (i / 32u)),
ref Unsafe.Add(ref arrayRef, sizeof(uint) * (i / 32)),
~((upperResult << 16) | lowerResult));
i += (uint)Vector128<byte>.Count * 2u;
i += Vector128<byte>.Count * 2;
valuesAsBytes = valuesAsBytes.Slice(Vector128<byte>.Count * 2);
}
}

Remainder:
for (; i < (uint)values.Length; i++)
for (; i < values.Length; i++)
{
if (values[(int)i])
if (values[i])
{
(uint byteIndex, uint bitOffset) = Math.DivRem(i, BitsPerByte);
array[byteIndex] |= (byte)(1 << (int)bitOffset);
(int byteIndex, int bitOffset) = Math.DivRem(i, BitsPerByte);
_array[byteIndex] |= (byte)(1 << bitOffset);
}
}

return array;
}

/// <summary>
Expand All @@ -303,8 +367,22 @@ ref Unsafe.Add(ref arrayRef, sizeof(uint) * (i / 32u)),
public BitArray(int[] values)
{
ArgumentNullException.ThrowIfNull(values);
if (values.Length > int.MaxValue / BitsPerInt32)
{
throw new ArgumentException(SR.Format(SR.Argument_ArrayTooLarge, BitsPerInt32), nameof(values));
}

_array = CreateArray(values, out _bitLength);
_bitLength = values.Length * BitsPerInt32;
_array = AllocateByteArray(_bitLength);

if (BitConverter.IsLittleEndian)
{
MemoryMarshal.AsBytes(values).CopyTo(_array);
}
else
{
BinaryPrimitives.ReverseEndianness(values, MemoryMarshal.Cast<byte, int>((Span<byte>)_array));
}
}

/// <summary>
Expand All @@ -322,30 +400,23 @@ public BitArray(int[] values)
/// This constructor is an <c>O(n)</c> operation, where <c>n</c> is the number of elements in <paramref name="values"/>.
/// </remarks>
public BitArray(ReadOnlySpan<int> values)
{
_array = CreateArray(values, out _bitLength);
}

private static byte[] CreateArray(ReadOnlySpan<int> values, out int bitLength)
{
if (values.Length > int.MaxValue / BitsPerInt32)
{
throw new ArgumentException(SR.Format(SR.Argument_ArrayTooLarge, BitsPerInt32), nameof(values));
}

bitLength = values.Length * BitsPerInt32;
byte[] array = AllocateByteArray(bitLength);
_bitLength = values.Length * BitsPerInt32;
_array = AllocateByteArray(_bitLength);

if (BitConverter.IsLittleEndian)
{
MemoryMarshal.AsBytes(values).CopyTo(array);
MemoryMarshal.AsBytes(values).CopyTo(_array);
}
else
{
BinaryPrimitives.ReverseEndianness(values, MemoryMarshal.Cast<byte, int>((Span<byte>)array));
BinaryPrimitives.ReverseEndianness(values, MemoryMarshal.Cast<byte, int>((Span<byte>)_array));
}

return array;
}

/// <summary>
Expand Down
Loading