From 24d49e5ebf97332024e9ee0580d8b2aeab906cf3 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Wed, 7 Oct 2020 15:42:51 +0100 Subject: [PATCH 01/10] Add AVX backed Block8x8F Transpose method --- .../Jpeg/Components/Block8x8F.Generated.cs | 6 +- .../Jpeg/Components/Block8x8F.Generated.tt | 6 +- .../Formats/Jpeg/Components/Block8x8F.cs | 83 +++++++++++++++++++ .../Jpeg/Components/FastFloatingPointDCT.cs | 6 +- .../BlockOperations/Block8x8F_Transpose.cs | 45 ++++++++++ .../Formats/Jpg/Block8x8FTests.cs | 22 ++++- 6 files changed, 157 insertions(+), 11 deletions(-) create mode 100644 tests/ImageSharp.Benchmarks/Codecs/Jpeg/BlockOperations/Block8x8F_Transpose.cs diff --git a/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.Generated.cs b/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.Generated.cs index f6f5903684..10cbee5e6f 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.Generated.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.Generated.cs @@ -10,12 +10,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components { internal partial struct Block8x8F { - /// - /// Transpose the block into the destination block. + /// + /// Fallback method to transpose a block into the destination block on non AVX supported CPUs. /// /// The destination block [MethodImpl(InliningOptions.ShortMethod)] - public void TransposeInto(ref Block8x8F d) + public void TransposeIntoFallback(ref Block8x8F d) { d.V0L.X = V0L.X; d.V1L.X = V0L.Y; diff --git a/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.Generated.tt b/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.Generated.tt index 6ee0540213..f47d9106ee 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.Generated.tt +++ b/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.Generated.tt @@ -23,12 +23,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components { internal partial struct Block8x8F { - /// - /// Transpose the block into the destination block. + /// + /// Fallback method to transpose a block into the destination block on non AVX supported CPUs. /// /// The destination block [MethodImpl(InliningOptions.ShortMethod)] - public void TransposeInto(ref Block8x8F d) + public void TransposeIntoFallback(ref Block8x8F d) { <# PushIndent(" "); diff --git a/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.cs b/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.cs index b7835d6706..d698e90b3c 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.cs @@ -6,6 +6,10 @@ using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +#if SUPPORTS_RUNTIME_INTRINSICS +using System.Runtime.Intrinsics; +using System.Runtime.Intrinsics.X86; +#endif using System.Text; // ReSharper disable InconsistentNaming @@ -596,5 +600,84 @@ private static void GuardBlockIndex(int idx) DebugGuard.MustBeLessThan(idx, Size, nameof(idx)); DebugGuard.MustBeGreaterThanOrEqualTo(idx, 0, nameof(idx)); } + + /// + /// Transpose the block into the destination block. + /// + /// The destination block + [MethodImpl(InliningOptions.ShortMethod)] + public void TransposeInto(ref Block8x8F d) + { +#if SUPPORTS_RUNTIME_INTRINSICS + if (Avx.IsSupported) + { + this.TransposeIntoAvx(ref d); + } + else +#endif + { + this.TransposeIntoFallback(ref d); + } + } + +#if SUPPORTS_RUNTIME_INTRINSICS + /// + /// AVX-only variant for executing . + /// + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void TransposeIntoAvx(ref Block8x8F d) + { + ref Vector256 r0 = ref Unsafe.As>(ref this.V0L); + ref Vector256 r1 = ref Unsafe.As>(ref this.V1L); + ref Vector256 r2 = ref Unsafe.As>(ref this.V2L); + ref Vector256 r3 = ref Unsafe.As>(ref this.V3L); + ref Vector256 r4 = ref Unsafe.As>(ref this.V4L); + ref Vector256 r5 = ref Unsafe.As>(ref this.V5L); + ref Vector256 r6 = ref Unsafe.As>(ref this.V6L); + ref Vector256 r7 = ref Unsafe.As>(ref this.V7L); + + Vector256 t0 = Avx.UnpackLow(r0, r1); + Vector256 t1 = Avx.UnpackHigh(r0, r1); + Vector256 t2 = Avx.UnpackLow(r2, r3); + Vector256 t3 = Avx.UnpackHigh(r2, r3); + Vector256 t4 = Avx.UnpackLow(r4, r5); + Vector256 t5 = Avx.UnpackHigh(r4, r5); + Vector256 t6 = Avx.UnpackLow(r6, r7); + Vector256 t7 = Avx.UnpackHigh(r6, r7); + + // Controls generated via + // _MM_SHUFFLE(fp3, fp2, fp1, fp0)(((fp3) << 6) | ((fp2) << 4) | ((fp1) << 2) | ((fp0))) + const byte Control1_0_1_0 = 0b1_00_01_00; // 1, 0, 1, 0 + const byte Control3_2_3_2 = 0b11_10_11_10; // 3, 2, 3, 2 + + r0 = Avx.Shuffle(t0, t2, Control1_0_1_0); + r1 = Avx.Shuffle(t0, t2, Control3_2_3_2); + r2 = Avx.Shuffle(t1, t3, Control1_0_1_0); + r3 = Avx.Shuffle(t1, t3, Control3_2_3_2); + r4 = Avx.Shuffle(t4, t6, Control1_0_1_0); + r5 = Avx.Shuffle(t4, t6, Control3_2_3_2); + r6 = Avx.Shuffle(t5, t7, Control1_0_1_0); + r7 = Avx.Shuffle(t5, t7, Control3_2_3_2); + + t0 = Avx.Permute2x128(r0, r4, 0x20); + t1 = Avx.Permute2x128(r1, r5, 0x20); + t2 = Avx.Permute2x128(r2, r6, 0x20); + t3 = Avx.Permute2x128(r3, r7, 0x20); + t4 = Avx.Permute2x128(r0, r4, 0x31); + t5 = Avx.Permute2x128(r1, r5, 0x31); + t6 = Avx.Permute2x128(r2, r6, 0x31); + t7 = Avx.Permute2x128(r3, r7, 0x31); + + Unsafe.As>(ref d.V0L) = t0; + Unsafe.As>(ref d.V1L) = t1; + Unsafe.As>(ref d.V2L) = t2; + Unsafe.As>(ref d.V3L) = t3; + Unsafe.As>(ref d.V4L) = t4; + Unsafe.As>(ref d.V5L) = t5; + Unsafe.As>(ref d.V6L) = t6; + Unsafe.As>(ref d.V7L) = t7; + } +#endif } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/FastFloatingPointDCT.cs b/src/ImageSharp/Formats/Jpeg/Components/FastFloatingPointDCT.cs index ee06f2bdeb..d0b373609b 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/FastFloatingPointDCT.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/FastFloatingPointDCT.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System.Numerics; @@ -50,8 +50,6 @@ internal static class FastFloatingPointDCT /// Temporary block provided by the caller public static void TransformIDCT(ref Block8x8F src, ref Block8x8F dest, ref Block8x8F temp) { - // TODO: Transpose is a bottleneck now. We need full AVX support to optimize it: - // https://github.com/dotnet/corefx/issues/22940 src.TransposeInto(ref temp); IDCT8x4_LeftPart(ref temp, ref dest); @@ -340,4 +338,4 @@ public static void TransformFDCT( dest.MultiplyInplace(C_0_125); } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/BlockOperations/Block8x8F_Transpose.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/BlockOperations/Block8x8F_Transpose.cs new file mode 100644 index 0000000000..d21e848357 --- /dev/null +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/BlockOperations/Block8x8F_Transpose.cs @@ -0,0 +1,45 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using BenchmarkDotNet.Attributes; +using SixLabors.ImageSharp.Formats.Jpeg.Components; + +namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg.BlockOperations +{ + public class Block8x8F_Transpose + { + private static readonly Block8x8F Source = Create8x8FloatData(); + + [Benchmark] + public void TransposeIntoVector4() + { + var dest = default(Block8x8F); + Source.TransposeIntoFallback(ref dest); + } + +#if SUPPORTS_RUNTIME_INTRINSICS + [Benchmark] + public void TransposeIntoAvx() + { + var dest = default(Block8x8F); + Source.TransposeIntoAvx(ref dest); + } +#endif + + private static Block8x8F Create8x8FloatData() + { + var result = new float[64]; + for (int i = 0; i < 8; i++) + { + for (int j = 0; j < 8; j++) + { + result[(i * 8) + j] = (i * 10) + j; + } + } + + var source = default(Block8x8F); + source.LoadFrom(result); + return source; + } + } +} diff --git a/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs index 722521f98d..050b1889ad 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs @@ -172,7 +172,7 @@ public void TransposeInto() source.LoadFrom(Create8x8FloatData()); var dest = default(Block8x8F); - source.TransposeInto(ref dest); + source.TransposeIntoFallback(ref dest); float[] actual = new float[64]; dest.ScaledCopyTo(actual); @@ -180,6 +180,26 @@ public void TransposeInto() Assert.Equal(expected, actual); } +#if SUPPORTS_RUNTIME_INTRINSICS + [Fact] + public void TransposeIntoAvx() + { + float[] expected = Create8x8FloatData(); + ReferenceImplementations.Transpose8x8(expected); + + var source = default(Block8x8F); + source.LoadFrom(Create8x8FloatData()); + + var dest = default(Block8x8F); + source.TransposeIntoAvx(ref dest); + + float[] actual = new float[64]; + dest.ScaledCopyTo(actual); + + Assert.Equal(expected, actual); + } +#endif + private class BufferHolder { public Block8x8F Buffer; From 7a5566248a82b6d5dc91812a67add12d83005a21 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Wed, 7 Oct 2020 17:42:50 +0100 Subject: [PATCH 02/10] Add variant 2 --- .../Formats/Jpeg/Components/Block8x8F.cs | 118 ++++++++++++++---- 1 file changed, 97 insertions(+), 21 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.cs b/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.cs index d698e90b3c..683308e354 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.cs @@ -628,14 +628,15 @@ public void TransposeInto(ref Block8x8F d) [MethodImpl(InliningOptions.ShortMethod)] public void TransposeIntoAvx(ref Block8x8F d) { - ref Vector256 r0 = ref Unsafe.As>(ref this.V0L); - ref Vector256 r1 = ref Unsafe.As>(ref this.V1L); - ref Vector256 r2 = ref Unsafe.As>(ref this.V2L); - ref Vector256 r3 = ref Unsafe.As>(ref this.V3L); - ref Vector256 r4 = ref Unsafe.As>(ref this.V4L); - ref Vector256 r5 = ref Unsafe.As>(ref this.V5L); - ref Vector256 r6 = ref Unsafe.As>(ref this.V6L); - ref Vector256 r7 = ref Unsafe.As>(ref this.V7L); +#if avxvariant1 + Vector256 r0 = Unsafe.As>(ref this.V0L); + Vector256 r1 = Unsafe.As>(ref this.V1L); + Vector256 r2 = Unsafe.As>(ref this.V2L); + Vector256 r3 = Unsafe.As>(ref this.V3L); + Vector256 r4 = Unsafe.As>(ref this.V4L); + Vector256 r5 = Unsafe.As>(ref this.V5L); + Vector256 r6 = Unsafe.As>(ref this.V6L); + Vector256 r7 = Unsafe.As>(ref this.V7L); Vector256 t0 = Avx.UnpackLow(r0, r1); Vector256 t1 = Avx.UnpackHigh(r0, r1); @@ -646,11 +647,9 @@ public void TransposeIntoAvx(ref Block8x8F d) Vector256 t6 = Avx.UnpackLow(r6, r7); Vector256 t7 = Avx.UnpackHigh(r6, r7); - // Controls generated via - // _MM_SHUFFLE(fp3, fp2, fp1, fp0)(((fp3) << 6) | ((fp2) << 4) | ((fp1) << 2) | ((fp0))) - const byte Control1_0_1_0 = 0b1_00_01_00; // 1, 0, 1, 0 - const byte Control3_2_3_2 = 0b11_10_11_10; // 3, 2, 3, 2 - + // Controls generated via _MM_SHUFFLE + const byte Control1_0_1_0 = 0b1000100; + const byte Control3_2_3_2 = 0b11101110; r0 = Avx.Shuffle(t0, t2, Control1_0_1_0); r1 = Avx.Shuffle(t0, t2, Control3_2_3_2); r2 = Avx.Shuffle(t1, t3, Control1_0_1_0); @@ -660,14 +659,16 @@ public void TransposeIntoAvx(ref Block8x8F d) r6 = Avx.Shuffle(t5, t7, Control1_0_1_0); r7 = Avx.Shuffle(t5, t7, Control3_2_3_2); - t0 = Avx.Permute2x128(r0, r4, 0x20); - t1 = Avx.Permute2x128(r1, r5, 0x20); - t2 = Avx.Permute2x128(r2, r6, 0x20); - t3 = Avx.Permute2x128(r3, r7, 0x20); - t4 = Avx.Permute2x128(r0, r4, 0x31); - t5 = Avx.Permute2x128(r1, r5, 0x31); - t6 = Avx.Permute2x128(r2, r6, 0x31); - t7 = Avx.Permute2x128(r3, r7, 0x31); + const byte Control0x20 = 0b100000; + const byte Control0x31 = 0b110001; + t0 = Avx.Permute2x128(r0, r4, Control0x20); + t1 = Avx.Permute2x128(r1, r5, Control0x20); + t2 = Avx.Permute2x128(r2, r6, Control0x20); + t3 = Avx.Permute2x128(r3, r7, Control0x20); + t4 = Avx.Permute2x128(r0, r4, Control0x31); + t5 = Avx.Permute2x128(r1, r5, Control0x31); + t6 = Avx.Permute2x128(r2, r6, Control0x31); + t7 = Avx.Permute2x128(r3, r7, Control0x31); Unsafe.As>(ref d.V0L) = t0; Unsafe.As>(ref d.V1L) = t1; @@ -677,6 +678,81 @@ public void TransposeIntoAvx(ref Block8x8F d) Unsafe.As>(ref d.V5L) = t5; Unsafe.As>(ref d.V6L) = t6; Unsafe.As>(ref d.V7L) = t7; +#else + Vector256 r0 = Avx.InsertVector128( + Unsafe.As>(ref this.V0L).ToVector256(), + Unsafe.As>(ref this.V4L), + 1); + + Vector256 r1 = Avx.InsertVector128( + Unsafe.As>(ref this.V1L).ToVector256(), + Unsafe.As>(ref this.V5L), + 1); + + Vector256 r2 = Avx.InsertVector128( + Unsafe.As>(ref this.V2L).ToVector256(), + Unsafe.As>(ref this.V6L), + 1); + + Vector256 r3 = Avx.InsertVector128( + Unsafe.As>(ref this.V3L).ToVector256(), + Unsafe.As>(ref this.V7L), + 1); + + Vector256 r4 = Avx.InsertVector128( + Unsafe.As>(ref this.V0R).ToVector256(), + Unsafe.As>(ref this.V4R), + 1); + + Vector256 r5 = Avx.InsertVector128( + Unsafe.As>(ref this.V1R).ToVector256(), + Unsafe.As>(ref this.V5R), + 1); + + Vector256 r6 = Avx.InsertVector128( + Unsafe.As>(ref this.V2R).ToVector256(), + Unsafe.As>(ref this.V6R), + 1); + + Vector256 r7 = Avx.InsertVector128( + Unsafe.As>(ref this.V3R).ToVector256(), + Unsafe.As>(ref this.V7R), + 1); + + Vector256 t0 = Avx.UnpackLow(r0, r1); + Vector256 t1 = Avx.UnpackHigh(r0, r1); + Vector256 t2 = Avx.UnpackLow(r2, r3); + Vector256 t3 = Avx.UnpackHigh(r2, r3); + Vector256 t4 = Avx.UnpackLow(r4, r5); + Vector256 t5 = Avx.UnpackHigh(r4, r5); + Vector256 t6 = Avx.UnpackLow(r6, r7); + Vector256 t7 = Avx.UnpackHigh(r6, r7); + + Vector256 v = Avx.Shuffle(t0, t2, 0x4E); + r0 = Avx.Blend(t0, v, 0xCC); + r1 = Avx.Blend(t2, v, 0x33); + + v = Avx.Shuffle(t1, t3, 0x4E); + r2 = Avx.Blend(t1, v, 0xCC); + r3 = Avx.Blend(t3, v, 0x33); + + v = Avx.Shuffle(t4, t6, 0x4E); + r4 = Avx.Blend(t4, v, 0xCC); + r5 = Avx.Blend(t6, v, 0x33); + + v = Avx.Shuffle(t5, t7, 0x4E); + r6 = Avx.Blend(t5, v, 0xCC); + r7 = Avx.Blend(t7, v, 0x33); + + Unsafe.As>(ref d.V0L) = r0; + Unsafe.As>(ref d.V1L) = r1; + Unsafe.As>(ref d.V2L) = r2; + Unsafe.As>(ref d.V3L) = r3; + Unsafe.As>(ref d.V4L) = r4; + Unsafe.As>(ref d.V5L) = r5; + Unsafe.As>(ref d.V6L) = r6; + Unsafe.As>(ref d.V7L) = r7; +#endif } #endif } From 3b2ade529df516efcedc21b79b7c06e390ef63de Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Wed, 7 Oct 2020 20:38:32 +0100 Subject: [PATCH 03/10] Update tests/ImageSharp.Benchmarks/Codecs/Jpeg/BlockOperations/Block8x8F_Transpose.cs Co-authored-by: Anton Firszov --- .../Codecs/Jpeg/BlockOperations/Block8x8F_Transpose.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/BlockOperations/Block8x8F_Transpose.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/BlockOperations/Block8x8F_Transpose.cs index d21e848357..ae1b23df92 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/BlockOperations/Block8x8F_Transpose.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/BlockOperations/Block8x8F_Transpose.cs @@ -10,7 +10,7 @@ public class Block8x8F_Transpose { private static readonly Block8x8F Source = Create8x8FloatData(); - [Benchmark] + [Benchmark(Baseline=true)] public void TransposeIntoVector4() { var dest = default(Block8x8F); From 093fbc4e577b74b5ff45ee676e67209d5c1edad4 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Wed, 7 Oct 2020 22:23:37 +0100 Subject: [PATCH 04/10] Use interleaving to prevent stack spills --- .../Formats/Jpeg/Components/Block8x8F.cs | 92 +++---------------- 1 file changed, 15 insertions(+), 77 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.cs b/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.cs index 683308e354..547e116230 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.cs @@ -628,57 +628,6 @@ public void TransposeInto(ref Block8x8F d) [MethodImpl(InliningOptions.ShortMethod)] public void TransposeIntoAvx(ref Block8x8F d) { -#if avxvariant1 - Vector256 r0 = Unsafe.As>(ref this.V0L); - Vector256 r1 = Unsafe.As>(ref this.V1L); - Vector256 r2 = Unsafe.As>(ref this.V2L); - Vector256 r3 = Unsafe.As>(ref this.V3L); - Vector256 r4 = Unsafe.As>(ref this.V4L); - Vector256 r5 = Unsafe.As>(ref this.V5L); - Vector256 r6 = Unsafe.As>(ref this.V6L); - Vector256 r7 = Unsafe.As>(ref this.V7L); - - Vector256 t0 = Avx.UnpackLow(r0, r1); - Vector256 t1 = Avx.UnpackHigh(r0, r1); - Vector256 t2 = Avx.UnpackLow(r2, r3); - Vector256 t3 = Avx.UnpackHigh(r2, r3); - Vector256 t4 = Avx.UnpackLow(r4, r5); - Vector256 t5 = Avx.UnpackHigh(r4, r5); - Vector256 t6 = Avx.UnpackLow(r6, r7); - Vector256 t7 = Avx.UnpackHigh(r6, r7); - - // Controls generated via _MM_SHUFFLE - const byte Control1_0_1_0 = 0b1000100; - const byte Control3_2_3_2 = 0b11101110; - r0 = Avx.Shuffle(t0, t2, Control1_0_1_0); - r1 = Avx.Shuffle(t0, t2, Control3_2_3_2); - r2 = Avx.Shuffle(t1, t3, Control1_0_1_0); - r3 = Avx.Shuffle(t1, t3, Control3_2_3_2); - r4 = Avx.Shuffle(t4, t6, Control1_0_1_0); - r5 = Avx.Shuffle(t4, t6, Control3_2_3_2); - r6 = Avx.Shuffle(t5, t7, Control1_0_1_0); - r7 = Avx.Shuffle(t5, t7, Control3_2_3_2); - - const byte Control0x20 = 0b100000; - const byte Control0x31 = 0b110001; - t0 = Avx.Permute2x128(r0, r4, Control0x20); - t1 = Avx.Permute2x128(r1, r5, Control0x20); - t2 = Avx.Permute2x128(r2, r6, Control0x20); - t3 = Avx.Permute2x128(r3, r7, Control0x20); - t4 = Avx.Permute2x128(r0, r4, Control0x31); - t5 = Avx.Permute2x128(r1, r5, Control0x31); - t6 = Avx.Permute2x128(r2, r6, Control0x31); - t7 = Avx.Permute2x128(r3, r7, Control0x31); - - Unsafe.As>(ref d.V0L) = t0; - Unsafe.As>(ref d.V1L) = t1; - Unsafe.As>(ref d.V2L) = t2; - Unsafe.As>(ref d.V3L) = t3; - Unsafe.As>(ref d.V4L) = t4; - Unsafe.As>(ref d.V5L) = t5; - Unsafe.As>(ref d.V6L) = t6; - Unsafe.As>(ref d.V7L) = t7; -#else Vector256 r0 = Avx.InsertVector128( Unsafe.As>(ref this.V0L).ToVector256(), Unsafe.As>(ref this.V4L), @@ -720,39 +669,28 @@ public void TransposeIntoAvx(ref Block8x8F d) 1); Vector256 t0 = Avx.UnpackLow(r0, r1); - Vector256 t1 = Avx.UnpackHigh(r0, r1); Vector256 t2 = Avx.UnpackLow(r2, r3); - Vector256 t3 = Avx.UnpackHigh(r2, r3); + Vector256 v = Avx.Shuffle(t0, t2, 0x4E); + Unsafe.As>(ref d.V0L) = Avx.Blend(t0, v, 0xCC); + Unsafe.As>(ref d.V1L) = Avx.Blend(t2, v, 0x33); + Vector256 t4 = Avx.UnpackLow(r4, r5); - Vector256 t5 = Avx.UnpackHigh(r4, r5); Vector256 t6 = Avx.UnpackLow(r6, r7); - Vector256 t7 = Avx.UnpackHigh(r6, r7); - - Vector256 v = Avx.Shuffle(t0, t2, 0x4E); - r0 = Avx.Blend(t0, v, 0xCC); - r1 = Avx.Blend(t2, v, 0x33); + v = Avx.Shuffle(t4, t6, 0x4E); + Unsafe.As>(ref d.V4L) = Avx.Blend(t4, v, 0xCC); + Unsafe.As>(ref d.V5L) = Avx.Blend(t6, v, 0x33); + Vector256 t1 = Avx.UnpackHigh(r0, r1); + Vector256 t3 = Avx.UnpackHigh(r2, r3); v = Avx.Shuffle(t1, t3, 0x4E); - r2 = Avx.Blend(t1, v, 0xCC); - r3 = Avx.Blend(t3, v, 0x33); - - v = Avx.Shuffle(t4, t6, 0x4E); - r4 = Avx.Blend(t4, v, 0xCC); - r5 = Avx.Blend(t6, v, 0x33); + Unsafe.As>(ref d.V2L) = Avx.Blend(t1, v, 0xCC); + Unsafe.As>(ref d.V3L) = Avx.Blend(t3, v, 0x33); + Vector256 t5 = Avx.UnpackHigh(r4, r5); + Vector256 t7 = Avx.UnpackHigh(r6, r7); v = Avx.Shuffle(t5, t7, 0x4E); - r6 = Avx.Blend(t5, v, 0xCC); - r7 = Avx.Blend(t7, v, 0x33); - - Unsafe.As>(ref d.V0L) = r0; - Unsafe.As>(ref d.V1L) = r1; - Unsafe.As>(ref d.V2L) = r2; - Unsafe.As>(ref d.V3L) = r3; - Unsafe.As>(ref d.V4L) = r4; - Unsafe.As>(ref d.V5L) = r5; - Unsafe.As>(ref d.V6L) = r6; - Unsafe.As>(ref d.V7L) = r7; -#endif + Unsafe.As>(ref d.V6L) = Avx.Blend(t5, v, 0xCC); + Unsafe.As>(ref d.V7L) = Avx.Blend(t7, v, 0x33); } #endif } From 8e5a59fa419d576bba03ab8cb1f96ff0681bd3f5 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Wed, 7 Oct 2020 23:10:53 +0100 Subject: [PATCH 05/10] Update Block8x8FTests.cs --- tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs index 050b1889ad..73a68063c0 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs @@ -163,7 +163,7 @@ public void Load_Store_IntArray() } [Fact] - public void TransposeInto() + public void TransposeIntoFallback() { float[] expected = Create8x8FloatData(); ReferenceImplementations.Transpose8x8(expected); From 6dae52e64b5f2a4f745be526d9dab40160b9e812 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Fri, 9 Oct 2020 17:02:13 +0100 Subject: [PATCH 06/10] First pass at HW feature tests Designed to fail to ensure RemoteExecutor is running. --- .../Formats/Jpg/Block8x8FTests.cs | 30 ++- .../FeatureTesting/FeatureTestRunner.cs | 232 ++++++++++++++++++ 2 files changed, 261 insertions(+), 1 deletion(-) create mode 100644 tests/ImageSharp.Tests/TestUtilities/FeatureTesting/FeatureTestRunner.cs diff --git a/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs index 73a68063c0..a09472b46f 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs @@ -8,7 +8,7 @@ using SixLabors.ImageSharp.Formats.Jpeg.Components; using SixLabors.ImageSharp.Tests.Formats.Jpg.Utils; - +using SixLabors.ImageSharp.Tests.TestUtilities; using Xunit; using Xunit.Abstractions; @@ -200,6 +200,34 @@ public void TransposeIntoAvx() } #endif + [Fact] + public void TransposeInto() + { + static void RunTest() + { + // Just testing this fails in CI. RemoteExecutor is not working on my machine. + Assert.True(false); + + float[] expected = Create8x8FloatData(); + ReferenceImplementations.Transpose8x8(expected); + + var source = default(Block8x8F); + source.LoadFrom(Create8x8FloatData()); + + var dest = default(Block8x8F); + source.TransposeInto(ref dest); + + float[] actual = new float[64]; + dest.ScaledCopyTo(actual); + + Assert.Equal(expected, actual); + } + + FeatureTestRunner.RunWithHwIntrinsicsFeature( + RunTest, + HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX); + } + private class BufferHolder { public Block8x8F Buffer; diff --git a/tests/ImageSharp.Tests/TestUtilities/FeatureTesting/FeatureTestRunner.cs b/tests/ImageSharp.Tests/TestUtilities/FeatureTesting/FeatureTestRunner.cs new file mode 100644 index 0000000000..8b5ed8d48b --- /dev/null +++ b/tests/ImageSharp.Tests/TestUtilities/FeatureTesting/FeatureTestRunner.cs @@ -0,0 +1,232 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Numerics; +#if SUPPORTS_RUNTIME_INTRINSICS +using System.Runtime.Intrinsics.X86; +#endif +using Microsoft.DotNet.RemoteExecutor; +using Xunit; +using Xunit.Abstractions; + +namespace SixLabors.ImageSharp.Tests.TestUtilities +{ + /// + /// Allows the testing against specific feature sets. + /// + public static class FeatureTestRunner + { + private static readonly char[] SplitChars = new[] { ',', ' ' }; + + /// + /// Allows the deserialization of parameters passed to the feature test. + /// + /// + /// This is required because does not allow + /// marshalling of fields so we cannot pass a wrapped + /// allowing automatic deserialization. + /// + /// + /// + /// The type to deserialize to. + /// The string value to deserialize. + /// The value. + public static T Deserialize(string value) + where T : IXunitSerializable + => BasicSerializer.Deserialize(value); + + // TODO: Write runner test and use this. + private static void AssertHwIntrinsicsFeatureDisabled(HwIntrinsics intrinsics) + { + switch (intrinsics) + { + case HwIntrinsics.DisableSIMD: + Assert.False(Vector.IsHardwareAccelerated); + break; +#if SUPPORTS_RUNTIME_INTRINSICS + case HwIntrinsics.DisableHWIntrinsic: + Assert.False(Vector.IsHardwareAccelerated); + break; + case HwIntrinsics.DisableSSE: + Assert.False(Sse.IsSupported); + break; + case HwIntrinsics.DisableSSE2: + Assert.False(Sse2.IsSupported); + break; + case HwIntrinsics.DisableAES: + Assert.False(Aes.IsSupported); + break; + case HwIntrinsics.DisablePCLMULQDQ: + Assert.False(Pclmulqdq.IsSupported); + break; + case HwIntrinsics.DisableSSE3: + Assert.False(Sse3.IsSupported); + break; + case HwIntrinsics.DisableSSSE3: + Assert.False(Ssse3.IsSupported); + break; + case HwIntrinsics.DisableSSE41: + Assert.False(Sse41.IsSupported); + break; + case HwIntrinsics.DisableSSE42: + Assert.False(Sse42.IsSupported); + break; + case HwIntrinsics.DisablePOPCNT: + Assert.False(Popcnt.IsSupported); + break; + case HwIntrinsics.DisableAVX: + Assert.False(Avx.IsSupported); + break; + case HwIntrinsics.DisableFMA: + Assert.False(Fma.IsSupported); + break; + case HwIntrinsics.DisableAVX2: + Assert.False(Avx2.IsSupported); + break; + case HwIntrinsics.DisableBMI1: + Assert.False(Bmi1.IsSupported); + break; + case HwIntrinsics.DisableBMI2: + Assert.False(Bmi2.IsSupported); + break; + case HwIntrinsics.DisableLZCNT: + Assert.False(Lzcnt.IsSupported); + break; +#endif + } + } + + /// + /// Runs the given test within an environment + /// where the given features. + /// + /// The test action to run. + /// The intrinsics features. + public static void RunWithHwIntrinsicsFeature( + Action action, + HwIntrinsics intrinsics) + { + if (!RemoteExecutor.IsSupported) + { + return; + } + + foreach (string intrinsic in intrinsics.ToFeatureCollection()) + { + var processStartInfo = new ProcessStartInfo(); + if (intrinsic != nameof(HwIntrinsics.AllowAll)) + { + processStartInfo.Environment[$"COMPlus_{intrinsic}"] = "0"; + } + + RemoteExecutor.Invoke( + action, + new RemoteInvokeOptions + { + StartInfo = processStartInfo + }) + .Dispose(); + } + } + + /// + /// Runs the given test within an environment + /// where the given features. + /// + /// The test action to run. + /// The intrinsics features. + /// The value to pass as a parameter to the test action. + public static void RunWithHwIntrinsicsFeature( + Action action, + HwIntrinsics intrinsics, + T serializable) + where T : IXunitSerializable + { + if (!RemoteExecutor.IsSupported) + { + return; + } + + foreach (string intrinsic in intrinsics.ToFeatureCollection()) + { + var processStartInfo = new ProcessStartInfo(); + if (intrinsic != nameof(HwIntrinsics.AllowAll)) + { + processStartInfo.Environment[$"COMPlus_Enable{intrinsic}"] = "0"; + } + + RemoteExecutor.Invoke( + action, + BasicSerializer.Serialize(serializable), + new RemoteInvokeOptions + { + StartInfo = processStartInfo + }) + .Dispose(); + } + } + + private static IEnumerable ToFeatureCollection(this HwIntrinsics intrinsics) + { + // Loop through and translate the given values into COMPlus equivaluents + var features = new List(); + var split = intrinsics.ToString("G").Split(SplitChars, StringSplitOptions.RemoveEmptyEntries).ToArray(); + foreach (string intrinsic in split) + { + switch (intrinsic) + { + case nameof(HwIntrinsics.DisableSIMD): + features.Add("FeatureSIMD"); + break; + + case nameof(HwIntrinsics.AllowAll): + + // Not a COMPlus value. We filter in calling method. + features.Add(nameof(HwIntrinsics.AllowAll)); + break; + + default: + features.Add(intrinsic.Replace("Disable", "Enable")); + break; + } + } + + return features; + } + } + + /// + /// See + /// + /// ends up impacting all SIMD support(including System.Numerics) + /// but not things like , , and . + /// + /// + [Flags] + public enum HwIntrinsics + { + // Use flags so we can pass multiple values without using params. + DisableSIMD = 0, + DisableHWIntrinsic = 1 << 0, + DisableSSE = 1 << 1, + DisableSSE2 = 1 << 2, + DisableAES = 1 << 3, + DisablePCLMULQDQ = 1 << 4, + DisableSSE3 = 1 << 5, + DisableSSSE3 = 1 << 6, + DisableSSE41 = 1 << 7, + DisableSSE42 = 1 << 8, + DisablePOPCNT = 1 << 9, + DisableAVX = 1 << 10, + DisableFMA = 1 << 11, + DisableAVX2 = 1 << 12, + DisableBMI1 = 1 << 13, + DisableBMI2 = 1 << 14, + DisableLZCNT = 1 << 15, + AllowAll = 1 << 16 + } +} From 9c648d72e46ade56eb277fb6c9ae59512289ec62 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Fri, 9 Oct 2020 17:09:02 +0100 Subject: [PATCH 07/10] Fix build --- tests/Directory.Build.targets | 4 ++-- .../TestUtilities/FeatureTesting/FeatureTestRunner.cs | 3 +-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/tests/Directory.Build.targets b/tests/Directory.Build.targets index e9e93a855f..4edc9fdff3 100644 --- a/tests/Directory.Build.targets +++ b/tests/Directory.Build.targets @@ -30,8 +30,8 @@ - - + + diff --git a/tests/ImageSharp.Tests/TestUtilities/FeatureTesting/FeatureTestRunner.cs b/tests/ImageSharp.Tests/TestUtilities/FeatureTesting/FeatureTestRunner.cs index 8b5ed8d48b..172c03d5a0 100644 --- a/tests/ImageSharp.Tests/TestUtilities/FeatureTesting/FeatureTestRunner.cs +++ b/tests/ImageSharp.Tests/TestUtilities/FeatureTesting/FeatureTestRunner.cs @@ -174,8 +174,7 @@ private static IEnumerable ToFeatureCollection(this HwIntrinsics intrins { // Loop through and translate the given values into COMPlus equivaluents var features = new List(); - var split = intrinsics.ToString("G").Split(SplitChars, StringSplitOptions.RemoveEmptyEntries).ToArray(); - foreach (string intrinsic in split) + foreach (string intrinsic in intrinsics.ToString("G").Split(SplitChars, StringSplitOptions.RemoveEmptyEntries)) { switch (intrinsic) { From 97c1846526600aa1a04fa65736cd9fdfb9207ad4 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Fri, 9 Oct 2020 17:16:30 +0100 Subject: [PATCH 08/10] Test windows only --- .github/workflows/build-and-test.yml | 32 ++++++++++++++-------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 0e093a8347..8fc2dd2bc9 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -14,26 +14,26 @@ jobs: strategy: matrix: options: - - os: ubuntu-latest - framework: netcoreapp3.1 - runtime: -x64 - codecov: false - - os: windows-latest - framework: netcoreapp3.1 - runtime: -x64 - codecov: true + # - os: ubuntu-latest + # framework: netcoreapp3.1 + # runtime: -x64 + # codecov: false + # - os: windows-latest + # framework: netcoreapp3.1 + # runtime: -x64 + # codecov: true - os: windows-latest framework: netcoreapp2.1 runtime: -x64 codecov: false - - os: windows-latest - framework: net472 - runtime: -x64 - codecov: false - - os: windows-latest - framework: net472 - runtime: -x86 - codecov: false + # - os: windows-latest + # framework: net472 + # runtime: -x64 + # codecov: false + # - os: windows-latest + # framework: net472 + # runtime: -x86 + # codecov: false runs-on: ${{matrix.options.os}} if: "!contains(github.event.head_commit.message, '[skip ci]')" From e33c1cdb6ba610ffc1430a29a126374851182078 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Fri, 9 Oct 2020 18:15:59 +0100 Subject: [PATCH 09/10] Use single test, enable runners --- .github/workflows/build-and-test.yml | 32 +++++++------- .../Formats/Jpg/Block8x8FTests.cs | 42 ------------------- 2 files changed, 16 insertions(+), 58 deletions(-) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 8fc2dd2bc9..0e093a8347 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -14,26 +14,26 @@ jobs: strategy: matrix: options: - # - os: ubuntu-latest - # framework: netcoreapp3.1 - # runtime: -x64 - # codecov: false - # - os: windows-latest - # framework: netcoreapp3.1 - # runtime: -x64 - # codecov: true + - os: ubuntu-latest + framework: netcoreapp3.1 + runtime: -x64 + codecov: false + - os: windows-latest + framework: netcoreapp3.1 + runtime: -x64 + codecov: true - os: windows-latest framework: netcoreapp2.1 runtime: -x64 codecov: false - # - os: windows-latest - # framework: net472 - # runtime: -x64 - # codecov: false - # - os: windows-latest - # framework: net472 - # runtime: -x86 - # codecov: false + - os: windows-latest + framework: net472 + runtime: -x64 + codecov: false + - os: windows-latest + framework: net472 + runtime: -x86 + codecov: false runs-on: ${{matrix.options.os}} if: "!contains(github.event.head_commit.message, '[skip ci]')" diff --git a/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs index a09472b46f..5482380885 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs @@ -5,7 +5,6 @@ // #define BENCHMARKING using System; using System.Diagnostics; - using SixLabors.ImageSharp.Formats.Jpeg.Components; using SixLabors.ImageSharp.Tests.Formats.Jpg.Utils; using SixLabors.ImageSharp.Tests.TestUtilities; @@ -162,52 +161,11 @@ public void Load_Store_IntArray() // PrintLinearData((Span)mirror); } - [Fact] - public void TransposeIntoFallback() - { - float[] expected = Create8x8FloatData(); - ReferenceImplementations.Transpose8x8(expected); - - var source = default(Block8x8F); - source.LoadFrom(Create8x8FloatData()); - - var dest = default(Block8x8F); - source.TransposeIntoFallback(ref dest); - - float[] actual = new float[64]; - dest.ScaledCopyTo(actual); - - Assert.Equal(expected, actual); - } - -#if SUPPORTS_RUNTIME_INTRINSICS - [Fact] - public void TransposeIntoAvx() - { - float[] expected = Create8x8FloatData(); - ReferenceImplementations.Transpose8x8(expected); - - var source = default(Block8x8F); - source.LoadFrom(Create8x8FloatData()); - - var dest = default(Block8x8F); - source.TransposeIntoAvx(ref dest); - - float[] actual = new float[64]; - dest.ScaledCopyTo(actual); - - Assert.Equal(expected, actual); - } -#endif - [Fact] public void TransposeInto() { static void RunTest() { - // Just testing this fails in CI. RemoteExecutor is not working on my machine. - Assert.True(false); - float[] expected = Create8x8FloatData(); ReferenceImplementations.Transpose8x8(expected); From 685693ae25857091e293f49d51e4456386d0e8fa Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Mon, 12 Oct 2020 14:13:05 +0100 Subject: [PATCH 10/10] Revert to 8e5a59f --- tests/Directory.Build.targets | 4 +- .../Formats/Jpg/Block8x8FTests.cs | 48 ++-- .../FeatureTesting/FeatureTestRunner.cs | 231 ------------------ 3 files changed, 33 insertions(+), 250 deletions(-) delete mode 100644 tests/ImageSharp.Tests/TestUtilities/FeatureTesting/FeatureTestRunner.cs diff --git a/tests/Directory.Build.targets b/tests/Directory.Build.targets index 4edc9fdff3..e9e93a855f 100644 --- a/tests/Directory.Build.targets +++ b/tests/Directory.Build.targets @@ -30,8 +30,8 @@ - - + + diff --git a/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs index 5482380885..73a68063c0 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs @@ -5,9 +5,10 @@ // #define BENCHMARKING using System; using System.Diagnostics; + using SixLabors.ImageSharp.Formats.Jpeg.Components; using SixLabors.ImageSharp.Tests.Formats.Jpg.Utils; -using SixLabors.ImageSharp.Tests.TestUtilities; + using Xunit; using Xunit.Abstractions; @@ -162,29 +163,42 @@ public void Load_Store_IntArray() } [Fact] - public void TransposeInto() + public void TransposeIntoFallback() { - static void RunTest() - { - float[] expected = Create8x8FloatData(); - ReferenceImplementations.Transpose8x8(expected); + float[] expected = Create8x8FloatData(); + ReferenceImplementations.Transpose8x8(expected); - var source = default(Block8x8F); - source.LoadFrom(Create8x8FloatData()); + var source = default(Block8x8F); + source.LoadFrom(Create8x8FloatData()); - var dest = default(Block8x8F); - source.TransposeInto(ref dest); + var dest = default(Block8x8F); + source.TransposeIntoFallback(ref dest); - float[] actual = new float[64]; - dest.ScaledCopyTo(actual); + float[] actual = new float[64]; + dest.ScaledCopyTo(actual); - Assert.Equal(expected, actual); - } + Assert.Equal(expected, actual); + } - FeatureTestRunner.RunWithHwIntrinsicsFeature( - RunTest, - HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX); +#if SUPPORTS_RUNTIME_INTRINSICS + [Fact] + public void TransposeIntoAvx() + { + float[] expected = Create8x8FloatData(); + ReferenceImplementations.Transpose8x8(expected); + + var source = default(Block8x8F); + source.LoadFrom(Create8x8FloatData()); + + var dest = default(Block8x8F); + source.TransposeIntoAvx(ref dest); + + float[] actual = new float[64]; + dest.ScaledCopyTo(actual); + + Assert.Equal(expected, actual); } +#endif private class BufferHolder { diff --git a/tests/ImageSharp.Tests/TestUtilities/FeatureTesting/FeatureTestRunner.cs b/tests/ImageSharp.Tests/TestUtilities/FeatureTesting/FeatureTestRunner.cs deleted file mode 100644 index 172c03d5a0..0000000000 --- a/tests/ImageSharp.Tests/TestUtilities/FeatureTesting/FeatureTestRunner.cs +++ /dev/null @@ -1,231 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. - -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; -using System.Numerics; -#if SUPPORTS_RUNTIME_INTRINSICS -using System.Runtime.Intrinsics.X86; -#endif -using Microsoft.DotNet.RemoteExecutor; -using Xunit; -using Xunit.Abstractions; - -namespace SixLabors.ImageSharp.Tests.TestUtilities -{ - /// - /// Allows the testing against specific feature sets. - /// - public static class FeatureTestRunner - { - private static readonly char[] SplitChars = new[] { ',', ' ' }; - - /// - /// Allows the deserialization of parameters passed to the feature test. - /// - /// - /// This is required because does not allow - /// marshalling of fields so we cannot pass a wrapped - /// allowing automatic deserialization. - /// - /// - /// - /// The type to deserialize to. - /// The string value to deserialize. - /// The value. - public static T Deserialize(string value) - where T : IXunitSerializable - => BasicSerializer.Deserialize(value); - - // TODO: Write runner test and use this. - private static void AssertHwIntrinsicsFeatureDisabled(HwIntrinsics intrinsics) - { - switch (intrinsics) - { - case HwIntrinsics.DisableSIMD: - Assert.False(Vector.IsHardwareAccelerated); - break; -#if SUPPORTS_RUNTIME_INTRINSICS - case HwIntrinsics.DisableHWIntrinsic: - Assert.False(Vector.IsHardwareAccelerated); - break; - case HwIntrinsics.DisableSSE: - Assert.False(Sse.IsSupported); - break; - case HwIntrinsics.DisableSSE2: - Assert.False(Sse2.IsSupported); - break; - case HwIntrinsics.DisableAES: - Assert.False(Aes.IsSupported); - break; - case HwIntrinsics.DisablePCLMULQDQ: - Assert.False(Pclmulqdq.IsSupported); - break; - case HwIntrinsics.DisableSSE3: - Assert.False(Sse3.IsSupported); - break; - case HwIntrinsics.DisableSSSE3: - Assert.False(Ssse3.IsSupported); - break; - case HwIntrinsics.DisableSSE41: - Assert.False(Sse41.IsSupported); - break; - case HwIntrinsics.DisableSSE42: - Assert.False(Sse42.IsSupported); - break; - case HwIntrinsics.DisablePOPCNT: - Assert.False(Popcnt.IsSupported); - break; - case HwIntrinsics.DisableAVX: - Assert.False(Avx.IsSupported); - break; - case HwIntrinsics.DisableFMA: - Assert.False(Fma.IsSupported); - break; - case HwIntrinsics.DisableAVX2: - Assert.False(Avx2.IsSupported); - break; - case HwIntrinsics.DisableBMI1: - Assert.False(Bmi1.IsSupported); - break; - case HwIntrinsics.DisableBMI2: - Assert.False(Bmi2.IsSupported); - break; - case HwIntrinsics.DisableLZCNT: - Assert.False(Lzcnt.IsSupported); - break; -#endif - } - } - - /// - /// Runs the given test within an environment - /// where the given features. - /// - /// The test action to run. - /// The intrinsics features. - public static void RunWithHwIntrinsicsFeature( - Action action, - HwIntrinsics intrinsics) - { - if (!RemoteExecutor.IsSupported) - { - return; - } - - foreach (string intrinsic in intrinsics.ToFeatureCollection()) - { - var processStartInfo = new ProcessStartInfo(); - if (intrinsic != nameof(HwIntrinsics.AllowAll)) - { - processStartInfo.Environment[$"COMPlus_{intrinsic}"] = "0"; - } - - RemoteExecutor.Invoke( - action, - new RemoteInvokeOptions - { - StartInfo = processStartInfo - }) - .Dispose(); - } - } - - /// - /// Runs the given test within an environment - /// where the given features. - /// - /// The test action to run. - /// The intrinsics features. - /// The value to pass as a parameter to the test action. - public static void RunWithHwIntrinsicsFeature( - Action action, - HwIntrinsics intrinsics, - T serializable) - where T : IXunitSerializable - { - if (!RemoteExecutor.IsSupported) - { - return; - } - - foreach (string intrinsic in intrinsics.ToFeatureCollection()) - { - var processStartInfo = new ProcessStartInfo(); - if (intrinsic != nameof(HwIntrinsics.AllowAll)) - { - processStartInfo.Environment[$"COMPlus_Enable{intrinsic}"] = "0"; - } - - RemoteExecutor.Invoke( - action, - BasicSerializer.Serialize(serializable), - new RemoteInvokeOptions - { - StartInfo = processStartInfo - }) - .Dispose(); - } - } - - private static IEnumerable ToFeatureCollection(this HwIntrinsics intrinsics) - { - // Loop through and translate the given values into COMPlus equivaluents - var features = new List(); - foreach (string intrinsic in intrinsics.ToString("G").Split(SplitChars, StringSplitOptions.RemoveEmptyEntries)) - { - switch (intrinsic) - { - case nameof(HwIntrinsics.DisableSIMD): - features.Add("FeatureSIMD"); - break; - - case nameof(HwIntrinsics.AllowAll): - - // Not a COMPlus value. We filter in calling method. - features.Add(nameof(HwIntrinsics.AllowAll)); - break; - - default: - features.Add(intrinsic.Replace("Disable", "Enable")); - break; - } - } - - return features; - } - } - - /// - /// See - /// - /// ends up impacting all SIMD support(including System.Numerics) - /// but not things like , , and . - /// - /// - [Flags] - public enum HwIntrinsics - { - // Use flags so we can pass multiple values without using params. - DisableSIMD = 0, - DisableHWIntrinsic = 1 << 0, - DisableSSE = 1 << 1, - DisableSSE2 = 1 << 2, - DisableAES = 1 << 3, - DisablePCLMULQDQ = 1 << 4, - DisableSSE3 = 1 << 5, - DisableSSSE3 = 1 << 6, - DisableSSE41 = 1 << 7, - DisableSSE42 = 1 << 8, - DisablePOPCNT = 1 << 9, - DisableAVX = 1 << 10, - DisableFMA = 1 << 11, - DisableAVX2 = 1 << 12, - DisableBMI1 = 1 << 13, - DisableBMI2 = 1 << 14, - DisableLZCNT = 1 << 15, - AllowAll = 1 << 16 - } -}