test: add tests for ReadOnlySpan.Pack (#73)

1 failing test: PackInt32Internal_Sse2_ShouldReturnCorrectInt32_GivenReadOnlySpan

This will fail the dotnet workflow.
This commit is contained in:
Oliver Booth 2023-04-02 23:14:38 +01:00
parent 78cebbce8b
commit 22d5f07215
No known key found for this signature in database
GPG Key ID: 20BEB9DC87961025
4 changed files with 527 additions and 224 deletions

View File

@ -1,4 +1,9 @@
using Microsoft.VisualStudio.TestTools.UnitTesting; #if NET5_0_OR_GREATER
using System.Runtime.Intrinsics.Arm;
using System.Runtime.Intrinsics.X86;
#endif
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq.AutoMock;
using X10D.Collections; using X10D.Collections;
using X10D.Core; using X10D.Core;
@ -144,93 +149,253 @@ public class SpanTest
} }
[TestMethod] [TestMethod]
public void Pack8Bit_Should_Pack_Correctly() public void PackByteInternal_Fallback_ShouldReturnCorrectByte_GivenReadOnlySpan_Using()
{ {
Span<bool> span = stackalloc bool[8] {true, true, false, false, true, true, false, false}; const byte expected = 0b00110011;
Assert.AreEqual(0b00110011, span.PackByte()); ReadOnlySpan<bool> span = stackalloc bool[8] {true, true, false, false, true, true, false, false};
byte actual = span.PackByteInternal_Fallback();
Assert.AreEqual(expected, actual);
}
#if NET5_0_OR_GREATER
[TestMethod]
public void PackByteInternal_Sse2_ShouldReturnCorrectByte_GivenReadOnlySpan_Using()
{
if (!Sse2.IsSupported)
{
return;
}
const byte expected = 0b00110011;
ReadOnlySpan<bool> span = stackalloc bool[8] {true, true, false, false, true, true, false, false};
byte actual = span.PackByteInternal_Sse2();
Assert.AreEqual(expected, actual);
} }
[TestMethod] [TestMethod]
public void Pack8Bit_Should_Pack_Correctly_Randomize() public void PackByteInternal_AdvSimd_ShouldReturnCorrectByte_GivenReadOnlySpan_Using()
{ {
var value = new Random().NextByte(); if (!AdvSimd.IsSupported)
{
return;
}
Span<bool> unpacks = stackalloc bool[8]; const byte expected = 0b00110011;
ReadOnlySpan<bool> span = stackalloc bool[8] {true, true, false, false, true, true, false, false};
value.Unpack(unpacks); byte actual = span.PackByteInternal_AdvSimd();
Assert.AreEqual(value, unpacks.PackByte()); Assert.AreEqual(expected, actual);
}
#endif
[TestMethod]
public void PackInt16_ShouldReturnSameAsPackByte_WhenSpanHasLength8()
{
ReadOnlySpan<bool> span = stackalloc bool[8] {true, true, false, false, true, true, false, false};
short expected = span.PackByte();
short actual = span.PackInt16();
Assert.AreEqual(expected, actual);
} }
[TestMethod] [TestMethod]
public void Pack16Bit_Should_Pack_Correctly() public void PackInt16Internal_Fallback_ShouldReturnCorrectInt16_GivenReadOnlySpan()
{
const short expected = 0b00101101_11010100;
ReadOnlySpan<bool> span = stackalloc bool[16]
{
false, false, true, false, true, false, true, true, true, false, true, true, false, true, false, false,
};
short actual = span.PackInt16Internal_Fallback();
Assert.AreEqual(expected, actual);
}
#if NET5_0_OR_GREATER
[TestMethod]
public void PackInt16Internal_Sse2_ShouldReturnCorrectInt16_GivenReadOnlySpan_Using()
{
if (!Sse2.IsSupported)
{
return;
}
const short expected = 0b00101101_11010100;
ReadOnlySpan<bool> span = stackalloc bool[16]
{
false, false, true, false, true, false, true, true, true, false, true, true, false, true, false, false,
};
short actual = span.PackInt16Internal_Sse2();
Assert.AreEqual(expected, actual);
}
#endif
[TestMethod]
public void PackInt32Internal_Fallback_ShouldReturnCorrectInt32_GivenReadOnlySpan()
{
const int expected = 0b01010101_10101010_01010101_10101010;
ReadOnlySpan<bool> span = stackalloc bool[32]
{
false, true, false, true, false, true, false, true, true, false, true, false, true, false, true, false, false,
true, false, true, false, true, false, true, true, false, true, false, true, false, true, false,
};
int actual = span.PackInt32Internal_Fallback();
Assert.AreEqual(expected, actual);
}
#if NET5_0_OR_GREATER
[TestMethod]
public void PackInt32Internal_Sse2_ShouldReturnCorrectInt32_GivenReadOnlySpan()
{
if (!Sse2.IsSupported)
{
return;
}
const int expected = 0b01010101_10101010_01010101_10101010;
ReadOnlySpan<bool> span = stackalloc bool[32]
{
false, true, false, true, false, true, false, true, true, false, true, false, true, false, true, false, false,
true, false, true, false, true, false, true, true, false, true, false, true, false, true, false,
};
int actual = span.PackInt32Internal_Sse2();
Assert.AreEqual(expected, actual);
}
[TestMethod]
public void PackInt32Internal_Avx2_ShouldReturnCorrectInt32_GivenReadOnlySpan()
{
if (!Avx2.IsSupported)
{
return;
}
const int expected = 0b01010101_10101010_01010101_10101010;
ReadOnlySpan<bool> span = stackalloc bool[32]
{
false, true, false, true, false, true, false, true, true, false, true, false, true, false, true, false, false,
true, false, true, false, true, false, true, true, false, true, false, true, false, true, false,
};
int actual = span.PackInt32Internal_Avx2();
Assert.AreEqual(expected, actual);
}
[TestMethod]
public void PackInt32Internal_AdvSimd_ShouldReturnCorrectInt32_GivenReadOnlySpan()
{
if (!AdvSimd.IsSupported)
{
return;
}
const int expected = 0b01010101_10101010_01010101_10101010;
ReadOnlySpan<bool> span = stackalloc bool[32]
{
false, true, false, true, false, true, false, true, true, false, true, false, true, false, true, false, false,
true, false, true, false, true, false, true, true, false, true, false, true, false, true, false,
};
int actual = span.PackInt32Internal_AdvSimd();
Assert.AreEqual(expected, actual);
}
#endif
[TestMethod]
public void PackInt32_ShouldReturnSameAsPackByte_WhenSpanHasLength8()
{
ReadOnlySpan<bool> span = stackalloc bool[8] {true, true, false, false, true, true, false, false};
int expected = span.PackByte();
int actual = span.PackInt32();
Assert.AreEqual(expected, actual);
}
[TestMethod]
public void PackInt32_ShouldReturnSameAsPackInt16_WhenSpanHasLength16()
{ {
ReadOnlySpan<bool> span = stackalloc bool[16] ReadOnlySpan<bool> span = stackalloc bool[16]
{ {
false, false, true, false, true, false, true, true, true, false, true, true, false, true, false, false, false, false, true, false, true, false, true, true, true, false, true, true, false, true, false, false,
}; };
Assert.AreEqual(0b00101101_11010100, span.PackInt16());
int expected = span.PackInt16();
int actual = span.PackInt32();
Assert.AreEqual(expected, actual);
} }
[TestMethod] [TestMethod]
public void Pack16Bit_Should_Pack_Correctly_Randomize() public void PackInt64_ShouldReturnCorrectInt64_GivenReadOnlySpan()
{ {
var value = new Random().NextInt16(); const long expected = 0b01010101_11010110_01101001_11010110_00010010_10010111_00101100_10100101;
ReadOnlySpan<bool> span = stackalloc bool[64]
Span<bool> unpacks = stackalloc bool[16];
value.Unpack(unpacks);
Assert.AreEqual(value, unpacks.PackInt16());
}
[TestMethod]
public void Pack32Bit_Should_Pack_Correctly()
{
ReadOnlySpan<bool> span = stackalloc bool[]
{
false, true, false, true, false, true, false, true, true, false, true, false, true, false, true, false, false,
true, false, true, false, true, false, true, true, false, true, false, true, false, true, false,
};
Assert.AreEqual(0b01010101_10101010_01010101_10101010, span.PackInt32());
}
[TestMethod]
public void Pack32Bit_Should_Pack_Correctly_Randomize()
{
var value = new Random().Next(int.MinValue, int.MaxValue);
Span<bool> unpacks = stackalloc bool[32];
value.Unpack(unpacks);
Assert.AreEqual(value, unpacks.PackInt32());
}
[TestMethod]
public void Pack64Bit_Should_Pack_Correctly()
{
ReadOnlySpan<bool> span = stackalloc bool[]
{ {
true, false, true, false, false, true, false, true, false, false, true, true, false, true, false, false, true, true, false, true, false, false, true, false, true, false, false, true, true, false, true, false, false, true,
true, true, false, true, false, false, true, false, true, false, false, true, false, false, false, false, true, true, true, false, true, false, false, true, false, true, false, false, true, false, false, false, false, true,
true, false, true, false, true, true, true, false, false, true, false, true, true, false, false, true, true, true, false, true, false, true, true, true, false, false, true, false, true, true, false, false, true, true,
false, true, false, true, true, true, false, true, false, true, false, true, false, false, true, false, true, true, true, false, true, false, true, false, true, false,
}; };
Assert.AreEqual(0b01010101_11010110_01101001_11010110_00010010_10010111_00101100_10100101, span.PackInt64());
long actual = span.PackInt64();
Assert.AreEqual(expected, actual);
} }
[TestMethod] [TestMethod]
public void Pack64Bit_Should_Pack_Correctly_Randomize() public void PackInt64_ShouldReturnSameAsPackByte_WhenSpanHasLength8()
{ {
var rand = new Random(); ReadOnlySpan<bool> span = stackalloc bool[8] {true, true, false, false, true, true, false, false};
long value = ((long)rand.Next() << 32) | (long)rand.Next();
Span<bool> unpacks = stackalloc bool[64]; long expected = span.PackByte();
long actual = span.PackInt64();
value.Unpack(unpacks); Assert.AreEqual(expected, actual);
}
Assert.AreEqual(value, unpacks.PackInt64()); [TestMethod]
public void PackInt64_ShouldReturnSameAsPackInt16_WhenSpanHasLength16()
{
ReadOnlySpan<bool> span = stackalloc bool[16]
{
false, false, true, false, true, false, true, true, true, false, true, true, false, true, false, false,
};
long expected = span.PackInt16();
long actual = span.PackInt64();
Assert.AreEqual(expected, actual);
}
[TestMethod]
public void PackInt64_ShouldReturnSameAsPackInt32_WhenSpanHasLength16()
{
ReadOnlySpan<bool> span = stackalloc bool[32]
{
false, true, false, true, false, true, false, true, true, false, true, false, true, false, true, false, false,
true, false, true, false, true, false, true, true, false, true, false, true, false, true, false,
};
long expected = span.PackInt32();
long actual = span.PackInt64();
Assert.AreEqual(expected, actual);
} }
private enum EnumByte : byte private enum EnumByte : byte

View File

@ -23,12 +23,6 @@ public static class SpanExtensions
#if NETCOREAPP3_0_OR_GREATER #if NETCOREAPP3_0_OR_GREATER
private const ulong IntegerPackingMagic = 0x0102040810204080; private const ulong IntegerPackingMagic = 0x0102040810204080;
[ExcludeFromCodeCoverage]
private static Vector64<ulong> IntegerPackingMagicV64
{
get => Vector64.Create(IntegerPackingMagic);
}
[ExcludeFromCodeCoverage] [ExcludeFromCodeCoverage]
private static Vector128<ulong> IntegerPackingMagicV128 private static Vector128<ulong> IntegerPackingMagicV128
{ {
@ -53,6 +47,7 @@ public static class SpanExtensions
/// <see langword="false" />. /// <see langword="false" />.
/// </returns> /// </returns>
/// <exception cref="ArgumentException">The size of <typeparamref name="T" /> is unsupported.</exception> /// <exception cref="ArgumentException">The size of <typeparamref name="T" /> is unsupported.</exception>
[Pure]
#if NETSTANDARD2_1 #if NETSTANDARD2_1
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
#else #else
@ -75,10 +70,11 @@ public static class SpanExtensions
/// <see langword="false" />. /// <see langword="false" />.
/// </returns> /// </returns>
/// <exception cref="ArgumentException">The size of <typeparamref name="T" /> is unsupported.</exception> /// <exception cref="ArgumentException">The size of <typeparamref name="T" /> is unsupported.</exception>
#if NETSTANDARD2_1 [Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)] #if NETCOREAPP3_0_OR_GREATER
#else
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
#else
[MethodImpl(MethodImplOptions.AggressiveInlining)]
#endif #endif
public static bool Contains<T>(this ReadOnlySpan<T> span, T value) where T : struct, Enum public static bool Contains<T>(this ReadOnlySpan<T> span, T value) where T : struct, Enum
{ {
@ -153,7 +149,11 @@ public static class SpanExtensions
/// <returns>An 8-bit unsigned integer containing the packed booleans.</returns> /// <returns>An 8-bit unsigned integer containing the packed booleans.</returns>
/// <exception cref="ArgumentException"><paramref name="source" /> contains more than 8 elements.</exception> /// <exception cref="ArgumentException"><paramref name="source" /> contains more than 8 elements.</exception>
[Pure] [Pure]
#if NETCOREAPP3_0_OR_GREATER
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
#else
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
#endif
public static byte PackByte(this Span<bool> source) public static byte PackByte(this Span<bool> source)
{ {
return PackByte((ReadOnlySpan<bool>)source); return PackByte((ReadOnlySpan<bool>)source);
@ -166,57 +166,42 @@ public static class SpanExtensions
/// <returns>An 8-bit unsigned integer containing the packed booleans.</returns> /// <returns>An 8-bit unsigned integer containing the packed booleans.</returns>
/// <exception cref="ArgumentException"><paramref name="source" /> contains more than 8 elements.</exception> /// <exception cref="ArgumentException"><paramref name="source" /> contains more than 8 elements.</exception>
[Pure] [Pure]
public static unsafe byte PackByte(this ReadOnlySpan<bool> source) #if NETCOREAPP3_0_OR_GREATER
{ [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
switch (source.Length)
{
case > 8: throw new ArgumentException("Source cannot contain more than 8 elements.", nameof(source));
case 8:
#if NETSTANDARD2_1
// TODO: Think of a way to do fast boolean correctness without using SIMD API.
goto default;
#else #else
// TODO: Acceleration in Big Endian environment. [MethodImpl(MethodImplOptions.AggressiveInlining)]
if (!BitConverter.IsLittleEndian) #endif
[ExcludeFromCodeCoverage]
public static byte PackByte(this ReadOnlySpan<bool> source)
{ {
goto default; if (source.Length > 8)
{
throw new ArgumentException(ExceptionMessages.SourceSpanIsTooLarge, nameof(source));
} }
fixed (bool* pSource = source) if (source.Length < 8)
{ {
// TODO: .NET 8.0 Wasm support. return PackByteInternal_Fallback(source);
}
#if NETCOREAPP3_0_OR_GREATER
if (!BitConverter.IsLittleEndian)
{
return PackByteInternal_Fallback(source);
}
if (Sse2.IsSupported) if (Sse2.IsSupported)
{ {
Vector128<byte> load = Sse2.LoadScalarVector128((ulong*)pSource).AsByte(); return PackByteInternal_Sse2(source);
return unchecked((byte)(IntegerPackingMagic * load.CorrectBoolean().AsUInt64().GetElement(0) >> 56));
} }
// Probably should remove this piece of code because it is untested, but I see no reason why it should fail
// unless vld1_u8 reverse positions of 8 bytes for some reason.
if (AdvSimd.IsSupported) if (AdvSimd.IsSupported)
{ {
// Hasn't been tested since March 6th 2023 (Reason: Unavailable hardware). return PackByteInternal_AdvSimd(source);
Vector64<byte> load = AdvSimd.LoadVector64((byte*)pSource);
return unchecked((byte)(IntegerPackingMagic * load.CorrectBoolean().AsUInt64().GetElement(0) >> 56));
}
goto default;
} }
#endif #endif
default:
byte result = 0;
for (var index = 0; index < source.Length; index++) return PackByteInternal_Fallback(source);
{
result |= (byte)(source[index] ? 1 << index : 0);
}
return result;
}
} }
/// <summary> /// <summary>
@ -239,52 +224,39 @@ public static class SpanExtensions
/// <returns>A 16-bit signed integer containing the packed booleans.</returns> /// <returns>A 16-bit signed integer containing the packed booleans.</returns>
/// <exception cref="ArgumentException"><paramref name="source" /> contains more than 16 elements.</exception> /// <exception cref="ArgumentException"><paramref name="source" /> contains more than 16 elements.</exception>
[Pure] [Pure]
public static unsafe short PackInt16(this ReadOnlySpan<bool> source) #if NETCOREAPP3_0_OR_GREATER
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
#else
[MethodImpl(MethodImplOptions.AggressiveInlining)]
#endif
[ExcludeFromCodeCoverage]
public static short PackInt16(this ReadOnlySpan<bool> source)
{ {
switch (source.Length) switch (source.Length)
{ {
case > 16: throw new ArgumentException("Source cannot contain more than than 16 elements.", nameof(source)); case > 16:
case 8: return PackByte(source); // Potential optimization throw new ArgumentException(ExceptionMessages.SourceSpanIsTooLarge, nameof(source));
case 8:
return PackByte(source);
case 16: case 16:
#if NETSTANDARD2_1
// TODO: Think of a way to do fast boolean correctness without using SIMD API.
goto default;
#else
// TODO: Acceleration in Big Endian environment.
if (!BitConverter.IsLittleEndian) if (!BitConverter.IsLittleEndian)
{ {
goto default; goto default;
} }
// TODO: AdvSimd implementation. #if NETCOREAPP3_0_OR_GREATER
// TODO: WasmSimd implementation.
if (Sse2.IsSupported) if (Sse2.IsSupported)
{ {
fixed (bool* pSource = source) return PackInt16Internal_Sse2(source);
{
Vector128<byte> load = Sse2.LoadVector128((byte*)pSource);
Vector128<ulong> correct = load.CorrectBoolean().AsUInt64();
Vector128<ulong> multiply = IntrinsicUtility.Multiply(IntegerPackingMagicV128, correct);
Vector128<ulong> shift = Sse2.ShiftRightLogical(multiply, 56);
return (short)(shift.GetElement(0) | (shift.GetElement(1) << 8));
} }
}
goto default;
#endif #endif
goto default;
case < 16:
return PackInt16Internal_Fallback(source);
default: default:
short result = 0; return PackInt16Internal_Fallback(source);
for (var index = 0; index < source.Length; index++)
{
result |= (short)(source[index] ? 1 << index : 0);
}
return result;
} }
} }
@ -295,7 +267,11 @@ public static class SpanExtensions
/// <returns>A 32-bit signed integer containing the packed booleans.</returns> /// <returns>A 32-bit signed integer containing the packed booleans.</returns>
/// <exception cref="ArgumentException"><paramref name="source" /> contains more than 32 elements.</exception> /// <exception cref="ArgumentException"><paramref name="source" /> contains more than 32 elements.</exception>
[Pure] [Pure]
#if NETCOREAPP3_0_OR_GREATER
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
#else
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
#endif
public static int PackInt32(this Span<bool> source) public static int PackInt32(this Span<bool> source)
{ {
return PackInt32((ReadOnlySpan<bool>)source); return PackInt32((ReadOnlySpan<bool>)source);
@ -308,28 +284,226 @@ public static class SpanExtensions
/// <returns>A 32-bit signed integer containing the packed booleans.</returns> /// <returns>A 32-bit signed integer containing the packed booleans.</returns>
/// <exception cref="ArgumentException"><paramref name="source" /> contains more than 32 elements.</exception> /// <exception cref="ArgumentException"><paramref name="source" /> contains more than 32 elements.</exception>
[Pure] [Pure]
public static unsafe int PackInt32(this ReadOnlySpan<bool> source) #if NETCOREAPP3_0_OR_GREATER
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
#else
[MethodImpl(MethodImplOptions.AggressiveInlining)]
#endif
[ExcludeFromCodeCoverage]
public static int PackInt32(this ReadOnlySpan<bool> source)
{ {
switch (source.Length) switch (source.Length)
{ {
case > 32: throw new ArgumentException("Source cannot contain more than than 32 elements.", nameof(source)); case > 32:
case 8: return PackByte(source); throw new ArgumentException(ExceptionMessages.SourceSpanIsTooLarge, nameof(source));
case 16: return PackInt16(source);
case 8:
return PackByte(source);
case 16:
return PackInt16(source);
case 32: case 32:
#if NETSTANDARD2_1 #if NETCOREAPP3_0_OR_GREATER
// TODO: Think of a way to do fast boolean correctness without using SIMD API.
goto default;
#else
// TODO: Acceleration in Big Endian environment.
if (!BitConverter.IsLittleEndian) if (!BitConverter.IsLittleEndian)
{ {
goto default; goto default;
} }
if (Avx2.IsSupported)
{
return PackInt32Internal_Avx2(source);
}
if (Sse2.IsSupported)
{
return PackInt32Internal_Sse2(source);
}
if (AdvSimd.IsSupported)
{
return PackInt32Internal_AdvSimd(source);
}
#endif
goto default;
default:
return PackInt32Internal_Fallback(source);
}
}
/// <summary>
/// Packs a <see cref="Span{T}"/> of booleans into a <see cref="long" />.
/// </summary>
/// <param name="source">The span of booleans to pack.</param>
/// <returns>A 64-bit signed integer containing the packed booleans.</returns>
/// <exception cref="ArgumentException"><paramref name="source" /> contains more than 64 elements.</exception>
[Pure]
#if NETCOREAPP3_0_OR_GREATER
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
#else
[MethodImpl(MethodImplOptions.AggressiveInlining)]
#endif
public static long PackInt64(this Span<bool> source)
{
return PackInt64((ReadOnlySpan<bool>)source);
}
/// <summary>
/// Packs a <see cref="ReadOnlySpan{T}"/> of booleans into a <see cref="long" />.
/// </summary>
/// <param name="source">The span of booleans to pack.</param>
/// <returns>A 64-bit signed integer containing the packed booleans.</returns>
/// <exception cref="ArgumentException"><paramref name="source" /> contains more than 64 elements.</exception>
[Pure]
#if NETCOREAPP3_0_OR_GREATER
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
#else
[MethodImpl(MethodImplOptions.AggressiveInlining)]
#endif
public static long PackInt64(this ReadOnlySpan<bool> source)
{
switch (source.Length)
{
case > 64: throw new ArgumentException(ExceptionMessages.SourceSpanIsTooLarge, nameof(source));
case 8: return PackByte(source);
case 16: return PackInt16(source);
case 32: return PackInt32(source);
// ReSharper disable once RedundantCast
case 64: return (long)PackInt32(source[..32]) | ((long)PackInt32(source[32..]) << 32);
default:
long result = 0;
for (var index = 0; index < source.Length; index++)
{
result |= source[index] ? 1U << index : 0;
}
return result;
}
}
[Pure]
#if NETCOREAPP3_0_OR_GREATER
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
#else
[MethodImpl(MethodImplOptions.AggressiveInlining)]
#endif
internal static byte PackByteInternal_Fallback(this ReadOnlySpan<bool> source)
{
byte result = 0;
for (var index = 0; index < source.Length; index++)
{
result |= (byte)(source[index] ? 1 << index : 0);
}
return result;
}
[Pure]
#if NETCOREAPP3_0_OR_GREATER
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
#else
[MethodImpl(MethodImplOptions.AggressiveInlining)]
#endif
internal static short PackInt16Internal_Fallback(this ReadOnlySpan<bool> source)
{
short result = 0;
for (var index = 0; index < source.Length; index++)
{
result |= (short)(source[index] ? 1 << index : 0);
}
return result;
}
[Pure]
#if NETCOREAPP3_0_OR_GREATER
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
#else
[MethodImpl(MethodImplOptions.AggressiveInlining)]
#endif
internal static int PackInt32Internal_Fallback(this ReadOnlySpan<bool> source)
{
var result = 0;
for (var index = 0; index < source.Length; index++)
{
result |= source[index] ? 1 << index : 0;
}
return result;
}
#if NETCOREAPP3_0_OR_GREATER
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
internal static byte PackByteInternal_Sse2(this ReadOnlySpan<bool> source)
{
unsafe
{
fixed (bool* pSource = source) fixed (bool* pSource = source)
{ {
if (Avx2.IsSupported) Vector128<byte> load = Sse2.LoadScalarVector128((ulong*)pSource).AsByte();
return unchecked((byte)(IntegerPackingMagic * load.CorrectBoolean().AsUInt64().GetElement(0) >> 56));
}
}
}
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
internal static short PackInt16Internal_Sse2(this ReadOnlySpan<bool> source)
{
unsafe
{
fixed (bool* pSource = source)
{
Vector128<byte> load = Sse2.LoadVector128((byte*)pSource);
Vector128<ulong> correct = load.CorrectBoolean().AsUInt64();
Vector128<ulong> multiply = IntrinsicUtility.Multiply(IntegerPackingMagicV128, correct);
Vector128<ulong> shift = Sse2.ShiftRightLogical(multiply, 56);
return (short)(shift.GetElement(0) | (shift.GetElement(1) << 8));
}
}
}
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
internal static int PackInt32Internal_AdvSimd(this ReadOnlySpan<bool> source)
{
unsafe
{
fixed (bool* pSource = source)
{
// TODO Hasn't been tested since March 6th 2023 (Reason: Unavailable hardware).
Vector128<ulong> vector1 = AdvSimd.LoadVector128((byte*)pSource).CorrectBoolean().AsUInt64();
Vector128<ulong> vector2 = AdvSimd.LoadVector128((byte*)(pSource + 16)).CorrectBoolean().AsUInt64();
Vector128<ulong> calc1 = IntrinsicUtility.Multiply(IntegerPackingMagicV128, vector1);
Vector128<ulong> calc2 = IntrinsicUtility.Multiply(IntegerPackingMagicV128, vector2);
calc1 = AdvSimd.ShiftRightLogical(calc1, 56);
calc2 = AdvSimd.ShiftRightLogical(calc2, 56);
Vector128<ulong> shift1 = AdvSimd.ShiftLogical(calc1, Vector128.Create(0, 8));
Vector128<ulong> shift2 = AdvSimd.ShiftLogical(calc2, Vector128.Create(16, 24));
return (int)(shift1.GetElement(0) | shift1.GetElement(1) | shift2.GetElement(0) | shift2.GetElement(1));
}
}
}
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
internal static int PackInt32Internal_Avx2(this ReadOnlySpan<bool> source)
{
unsafe
{
fixed (bool* pSource = source)
{ {
Vector256<byte> load = Avx.LoadVector256((byte*)pSource); Vector256<byte> load = Avx.LoadVector256((byte*)pSource);
Vector256<ulong> correct = load.CorrectBoolean().AsUInt64(); Vector256<ulong> correct = load.CorrectBoolean().AsUInt64();
@ -345,8 +519,16 @@ public static class SpanExtensions
return (int)or2.GetElement(0); return (int)or2.GetElement(0);
} }
}
}
if (Sse2.IsSupported) [Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
internal static int PackInt32Internal_Sse2(this ReadOnlySpan<bool> source)
{
unsafe
{
fixed (bool* pSource = source)
{ {
Vector128<byte> load = Sse2.LoadVector128((byte*)pSource); Vector128<byte> load = Sse2.LoadVector128((byte*)pSource);
Vector128<ulong> correct = load.CorrectBoolean().AsUInt64(); Vector128<ulong> correct = load.CorrectBoolean().AsUInt64();
@ -367,84 +549,28 @@ public static class SpanExtensions
return (int)or2.GetElement(0); return (int)or2.GetElement(0);
} }
}
}
if (AdvSimd.IsSupported) #if NET5_0_OR_GREATER
// dotcover disable
//NOSONAR
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
internal static byte PackByteInternal_AdvSimd(this ReadOnlySpan<bool> source)
{ {
// Hasn't been tested since March 6th 2023 (Reason: Unavailable hardware). unsafe
Vector128<ulong> vector1 = AdvSimd.LoadVector128((byte*)pSource).CorrectBoolean().AsUInt64();
Vector128<ulong> vector2 = AdvSimd.LoadVector128((byte*)(pSource + 16)).CorrectBoolean().AsUInt64();
Vector128<ulong> calc1 = IntrinsicUtility.Multiply(IntegerPackingMagicV128, vector1);
Vector128<ulong> calc2 = IntrinsicUtility.Multiply(IntegerPackingMagicV128, vector2);
calc1 = AdvSimd.ShiftRightLogical(calc1, 56);
calc2 = AdvSimd.ShiftRightLogical(calc2, 56);
Vector128<ulong> shift1 = AdvSimd.ShiftLogical(calc1, Vector128.Create(0, 8));
Vector128<ulong> shift2 = AdvSimd.ShiftLogical(calc2, Vector128.Create(16, 24));
return (int)(shift1.GetElement(0) | shift1.GetElement(1) | shift2.GetElement(0) | shift2.GetElement(1));
}
else
{ {
goto default; fixed (bool* pSource = source)
{
// TODO Hasn't been tested since March 6th 2023 (Reason: Unavailable hardware).
Vector64<byte> load = AdvSimd.LoadVector64((byte*)pSource);
return unchecked((byte)(IntegerPackingMagic * load.CorrectBoolean().AsUInt64().GetElement(0) >> 56));
} }
} }
}
//NOSONAR
// dotcover enable
#endif
#endif #endif
default:
int result = 0;
for (var i = 0; i < source.Length; i++)
{
result |= source[i] ? 1 << i : 0;
}
return result;
}
}
/// <summary>
/// Packs a <see cref="Span{T}"/> of booleans into a <see cref="long" />.
/// </summary>
/// <param name="source">The span of booleans to pack.</param>
/// <returns>A 64-bit signed integer containing the packed booleans.</returns>
/// <exception cref="ArgumentException"><paramref name="source" /> contains more than 64 elements.</exception>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static long PackInt64(this Span<bool> source)
{
return PackInt64((ReadOnlySpan<bool>)source);
}
/// <summary>
/// Packs a <see cref="ReadOnlySpan{T}"/> of booleans into a <see cref="long" />.
/// </summary>
/// <param name="source">The span of booleans to pack.</param>
/// <returns>A 64-bit signed integer containing the packed booleans.</returns>
/// <exception cref="ArgumentException"><paramref name="source" /> contains more than 64 elements.</exception>
[Pure]
public static unsafe long PackInt64(this ReadOnlySpan<bool> source)
{
switch (source.Length)
{
case > 64: throw new ArgumentException("Source cannot contain more than than 64 elements.", nameof(source));
case 8: return PackByte(source);
case 16: return PackInt16(source);
case 32: return PackInt32(source);
case 64:
// TODO: Reimplement when Vector512 is in standard API.
return (long)PackInt32(source[..32]) | ((long)PackInt32(source[32..]) << 32);
default:
long result = 0;
for (var index = 0; index < source.Length; index++)
{
result |= source[index] ? 1U << index : 0;
}
return result;
}
}
} }

View File

@ -176,6 +176,15 @@ namespace X10D {
} }
} }
/// <summary>
/// Looks up a localized string similar to The source contains too many elements..
/// </summary>
internal static string SourceSpanIsTooLarge {
get {
return ResourceManager.GetString("SourceSpanIsTooLarge", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to The stream does not support reading.. /// Looks up a localized string similar to The stream does not support reading..
/// </summary> /// </summary>

View File

@ -153,6 +153,9 @@
<data name="LengthGreaterThanOrEqualTo0" xml:space="preserve"> <data name="LengthGreaterThanOrEqualTo0" xml:space="preserve">
<value>Length must be greater than or equal to 0.</value> <value>Length must be greater than or equal to 0.</value>
</data> </data>
<data name="SourceSpanIsTooLarge" xml:space="preserve">
<value>The source contains too many elements.</value>
</data>
<data name="StreamDoesNotSupportReading" xml:space="preserve"> <data name="StreamDoesNotSupportReading" xml:space="preserve">
<value>The stream does not support reading.</value> <value>The stream does not support reading.</value>
</data> </data>