diff --git a/X10D.Tests/src/Core/IntrinsicTests.cs b/X10D.Tests/src/Core/IntrinsicTests.cs new file mode 100644 index 0000000..d0b1485 --- /dev/null +++ b/X10D.Tests/src/Core/IntrinsicTests.cs @@ -0,0 +1,86 @@ +#if NET6_0_OR_GREATER +using System.Runtime.Intrinsics; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; +using X10D.Core; + +namespace X10D.Tests.Core; + +[TestClass] +public class IntrinsicTests +{ + [TestMethod] + public void CorrectBoolean_ShouldReturnExpectedVector64Result_GivenInputVector() + { + var mock = new Mock(); + mock.Setup(provider => provider.IsSupported).Returns(true); + + var inputVector = Vector64.Create(0, 1, 2, 0, 3, 0, 0, (byte)4); + var expectedResult = Vector64.Create(0, 1, 1, 0, 1, 0, 0, (byte)1); + + Vector64 result = inputVector.CorrectBoolean(); + + Assert.AreEqual(expectedResult, result); + } + + [TestMethod] + public void CorrectBoolean_ShouldReturnExpectedVector128Result_GivenInputVector() + { + var mock = new Mock(); + mock.Setup(provider => provider.IsSupported).Returns(true); + + var inputVector = Vector128.Create(0, 1, 2, 0, 3, 0, 0, 4, 5, 0, 0, 6, 0, 0, 7, (byte)8); + var expectedResult = Vector128.Create(0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0, 1, (byte)1); + + Vector128 result = inputVector.CorrectBooleanInternal(mock.Object); + + Assert.AreEqual(expectedResult, result); + } + + [TestMethod] + public void CorrectBoolean_ShouldReturnExpectedVector128Result_WhenSse2NotSupported() + { + var mock = new Mock(); + mock.Setup(provider => provider.IsSupported).Returns(false); + + var inputVector = Vector128.Create(0, 1, 2, 0, 3, 0, 0, 4, 5, 0, 0, 6, 0, 0, 7, (byte)8); + var expectedResult = Vector128.Create(0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0, 1, (byte)1); + + Vector128 result = inputVector.CorrectBooleanInternal(mock.Object); + + Assert.AreEqual(expectedResult, result); + } + + [TestMethod] + public void CorrectBoolean_ShouldReturnExpectedVector256Result_GivenInputVector() + { + var mock = new Mock(); + mock.Setup(provider => provider.IsSupported).Returns(true); + + var inputVector = Vector256.Create(0, 1, 2, 0, 3, 0, 0, 4, 5, 0, 0, 6, 0, 0, 7, 8, 0, 1, 2, 0, 3, 0, 0, 4, 5, 0, 0, 6, 0, + 0, 7, (byte)8); + var expectedResult = Vector256.Create(0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0, 1, 1, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, + 0, 0, 1, (byte)1); + + Vector256 result = inputVector.CorrectBooleanInternal(mock.Object); + + Assert.AreEqual(expectedResult, result); + } + + [TestMethod] + public void CorrectBoolean_ShouldReturnExpectedVector256Result_WhenSse2NotSupported() + { + var mock = new Mock(); + mock.Setup(provider => provider.IsSupported).Returns(false); + + var inputVector = Vector256.Create(0, 1, 2, 0, 3, 0, 0, 4, 5, 0, 0, 6, 0, 0, 7, 8, 0, 1, 2, 0, 3, 0, 0, 4, 5, 0, 0, 6, 0, + 0, 7, (byte)8); + var expectedResult = Vector256.Create(0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0, 1, 1, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, + 0, 0, 1, (byte)1); + + Vector256 result = inputVector.CorrectBooleanInternal(mock.Object); + + Assert.AreEqual(expectedResult, result); + } +} +#endif diff --git a/X10D/src/Core/IntrinsicExtensions.cs b/X10D/src/Core/IntrinsicExtensions.cs index bcd8cfd..5ef9d3c 100644 --- a/X10D/src/Core/IntrinsicExtensions.cs +++ b/X10D/src/Core/IntrinsicExtensions.cs @@ -1,5 +1,5 @@ -#if NETCOREAPP3_0_OR_GREATER - +#if NETCOREAPP3_0_OR_GREATER +using System.Diagnostics.CodeAnalysis; using System.Diagnostics.Contracts; using System.Runtime.CompilerServices; using System.Runtime.Intrinsics; @@ -35,15 +35,15 @@ public static class IntrinsicExtensions // TODO: AdvSimd implementation. // TODO: WasmSimd implementation. (?) - var output = IntrinsicUtility.GetUninitializedVector64(); + Vector64 output = IntrinsicUtility.GetUninitializedVector64(); - for (int i = 0; i < Vector64.Count; i++) + for (var i = 0; i < Vector64.Count; i++) { - ref var writeElement = ref Unsafe.Add(ref Unsafe.As, byte>(ref output), i); + ref byte writeElement = ref Unsafe.Add(ref Unsafe.As, byte>(ref output), i); #if NET7_0_OR_GREATER writeElement = vector[i] == 0 ? (byte)0 : (byte)1; #else - var element = Unsafe.Add(ref Unsafe.As, byte>(ref vector), i); + byte element = Unsafe.Add(ref Unsafe.As, byte>(ref vector), i); writeElement = element == 0 ? (byte)0 : (byte)1; #endif } @@ -68,28 +68,10 @@ public static class IntrinsicExtensions /// [Pure] [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + [ExcludeFromCodeCoverage] public static Vector128 CorrectBoolean(this Vector128 vector) { - if (Sse2.IsSupported) - { - var cmp = Sse2.CompareEqual(vector, Vector128.Zero); - var result = Sse2.AndNot(cmp, Vector128.Create((byte)1)); - - return result; - } - - // TODO: AdvSimd implementation. - // TODO: WasmSimd implementation. - - var output = IntrinsicUtility.GetUninitializedVector128(); - - for (int i = 0; i < Vector128.Count; i++) - { - Unsafe.Add(ref Unsafe.As, byte>(ref output), i) = - Unsafe.Add(ref Unsafe.As, byte>(ref vector), i) == 0 ? (byte)0 : (byte)1; - } - - return output; + return CorrectBooleanInternal(vector, new SystemSse2SupportProvider()); } /// @@ -109,25 +91,10 @@ public static class IntrinsicExtensions /// [Pure] [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + [ExcludeFromCodeCoverage] public static Vector256 CorrectBoolean(this Vector256 vector) { - if (Avx2.IsSupported) - { - var cmp = Avx2.CompareEqual(vector, Vector256.Zero); - var result = Avx2.AndNot(cmp, Vector256.Create((byte)1)); - - return result; - } - - var output = IntrinsicUtility.GetUninitializedVector256(); - - for (int i = 0; i < Vector256.Count; i++) - { - Unsafe.Add(ref Unsafe.As, byte>(ref output), i) = - Unsafe.Add(ref Unsafe.As, byte>(ref vector), i) == 0 ? (byte)0 : (byte)1; - } - - return output; + return CorrectBooleanInternal(vector, new SystemAvx2SupportProvider()); } /// @@ -162,5 +129,58 @@ public static class IntrinsicExtensions return output; } + + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + internal static Vector128 CorrectBooleanInternal(this Vector128 vector, ISse2SupportProvider? sse2SupportProvider) + { + sse2SupportProvider ??= new SystemSse2SupportProvider(); + + if (sse2SupportProvider.IsSupported) + { + Vector128 cmp = Sse2.CompareEqual(vector, Vector128.Zero); + Vector128 result = Sse2.AndNot(cmp, Vector128.Create((byte)1)); + + return result; + } + + // TODO: AdvSimd implementation. + // TODO: WasmSimd implementation. + + Vector128 output = IntrinsicUtility.GetUninitializedVector128(); + + for (var index = 0; index < Vector128.Count; index++) + { + Unsafe.Add(ref Unsafe.As, byte>(ref output), index) = + Unsafe.Add(ref Unsafe.As, byte>(ref vector), index) == 0 ? (byte)0 : (byte)1; + } + + return output; + } + + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + internal static Vector256 CorrectBooleanInternal(this Vector256 vector, IAvx2SupportProvider? supportProvider) + { + supportProvider ??= new SystemAvx2SupportProvider(); + + if (supportProvider.IsSupported) + { + Vector256 cmp = Avx2.CompareEqual(vector, Vector256.Zero); + Vector256 result = Avx2.AndNot(cmp, Vector256.Create((byte)1)); + + return result; + } + + Vector256 output = IntrinsicUtility.GetUninitializedVector256(); + + for (var index = 0; index < Vector256.Count; index++) + { + Unsafe.Add(ref Unsafe.As, byte>(ref output), index) = + Unsafe.Add(ref Unsafe.As, byte>(ref vector), index) == 0 ? (byte)0 : (byte)1; + } + + return output; + } } #endif diff --git a/X10D/src/Core/IntrinsicUtility.cs b/X10D/src/Core/IntrinsicUtility.cs index 94d5fd5..005c735 100644 --- a/X10D/src/Core/IntrinsicUtility.cs +++ b/X10D/src/Core/IntrinsicUtility.cs @@ -3,7 +3,6 @@ using System.Diagnostics.Contracts; using System.Runtime.CompilerServices; using System.Runtime.Intrinsics; -using System.Runtime.Intrinsics.Arm; using System.Runtime.Intrinsics.X86; namespace X10D.Core; diff --git a/X10D/src/ISse2SupportProvider.cs b/X10D/src/ISse2SupportProvider.cs new file mode 100644 index 0000000..1af720f --- /dev/null +++ b/X10D/src/ISse2SupportProvider.cs @@ -0,0 +1,13 @@ +namespace X10D; + +/// +/// Represents an object which provides the status of the support of SSE2 instructions. +/// +public interface ISse2SupportProvider +{ + /// + /// Gets a value indicating whether SSE2 instructions are supported on this platform. + /// + /// if SSE2 instructions are supported; otherwise, . + bool IsSupported { get; } +} diff --git a/X10D/src/SystemSse2SupportProvider.cs b/X10D/src/SystemSse2SupportProvider.cs new file mode 100644 index 0000000..20c0337 --- /dev/null +++ b/X10D/src/SystemSse2SupportProvider.cs @@ -0,0 +1,18 @@ +#if NETCOREAPP3_0_OR_GREATER +using System.Runtime.Intrinsics.X86; +#endif + +namespace X10D; + +internal struct SystemSse2SupportProvider : ISse2SupportProvider +{ + /// + public bool IsSupported + { +#if NETCOREAPP3_0_OR_GREATER + get => Sse2.IsSupported; +#else + get => false; +#endif + } +}