Skip to content

Commit

Permalink
Vector.As<TFrom, TTo> (#47150)
Browse files Browse the repository at this point in the history
* Implement Vector.As using Unsafe.As.

* Ref source for Vector.As.

* Mark managed impl with intrinsic and aggressive inlining.

* Hwintrinsic definition

* Hwintrinsic impl

* Add missing break.

* Make extension method.

* Add test for unsupported As types.

* Add test for op_explicit and cast.

* Reorder intrinsic checks.
  • Loading branch information
huoyaoyuan authored Feb 3, 2021
1 parent 54f0d4b commit fabaed2
Show file tree
Hide file tree
Showing 7 changed files with 161 additions and 12 deletions.
42 changes: 30 additions & 12 deletions src/coreclr/jit/simdashwintrinsic.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -388,6 +388,15 @@ GenTree* Compiler::impSimdAsHWIntrinsicSpecial(NamedIntrinsic intrinsic,
// We should have already exited early if SSE2 isn't supported
assert(compIsaSupportedDebugOnly(InstructionSet_SSE2));

// Vector<T>, when 32-bytes, requires at least AVX2
assert(!isVectorT256 || compIsaSupportedDebugOnly(InstructionSet_AVX2));
#elif defined(TARGET_ARM64)
// We should have already exited early if AdvSimd isn't supported
assert(compIsaSupportedDebugOnly(InstructionSet_AdvSimd));
#else
#error Unsupported platform
#endif // !TARGET_XARCH && !TARGET_ARM64

switch (intrinsic)
{
#if defined(TARGET_X86)
Expand All @@ -405,6 +414,23 @@ GenTree* Compiler::impSimdAsHWIntrinsicSpecial(NamedIntrinsic intrinsic,
}
#endif // TARGET_X86

#if defined(TARGET_XARCH)
case NI_VectorT256_As:
#endif // TARGET_XARCH
case NI_VectorT128_As:
{
unsigned retSimdSize;
var_types retBaseType = getBaseTypeAndSizeOfSIMDType(sig->retTypeSigClass, &retSimdSize);

if (!varTypeIsArithmetic(retBaseType) || (retSimdSize == 0))
{
// We get here if the return type is an unsupported type
return nullptr;
}
break;
}

#if defined(TARGET_XARCH)
case NI_VectorT128_Dot:
{
if (!compOpportunisticallyDependsOn(InstructionSet_SSE41))
Expand All @@ -417,23 +443,15 @@ GenTree* Compiler::impSimdAsHWIntrinsicSpecial(NamedIntrinsic intrinsic,
}
break;
}
#endif // TARGET_XARCH

default:
{
// Most intrinsics have some path that works even if only SSE2 is available
// Most intrinsics have some path that works even if only SSE2/AdvSimd is available
break;
}
}

// Vector<T>, when 32-bytes, requires at least AVX2
assert(!isVectorT256 || compIsaSupportedDebugOnly(InstructionSet_AVX2));
#elif defined(TARGET_ARM64)
// We should have already exited early if AdvSimd isn't supported
assert(compIsaSupportedDebugOnly(InstructionSet_AdvSimd));
#else
#error Unsupported platform
#endif // !TARGET_XARCH && !TARGET_ARM64

GenTree* copyBlkDst = nullptr;
GenTree* copyBlkSrc = nullptr;

Expand Down Expand Up @@ -563,10 +581,10 @@ GenTree* Compiler::impSimdAsHWIntrinsicSpecial(NamedIntrinsic intrinsic,
{
assert(newobjThis == nullptr);

bool isOpExplicit = (intrinsic == NI_VectorT128_op_Explicit);
bool isOpExplicit = (intrinsic == NI_VectorT128_op_Explicit) || (intrinsic == NI_VectorT128_As);

#if defined(TARGET_XARCH)
isOpExplicit |= (intrinsic == NI_VectorT256_op_Explicit);
isOpExplicit |= (intrinsic == NI_VectorT256_op_Explicit) || (intrinsic == NI_VectorT256_As);
#endif

if (isOpExplicit)
Expand Down
1 change: 1 addition & 0 deletions src/coreclr/jit/simdashwintrinsiclistarm64.h
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ SIMD_AS_HWINTRINSIC_ID(Vector4, SquareRoot,
// Vector<T> Intrinsics
SIMD_AS_HWINTRINSIC_ID(VectorT128, Abs, 1, {NI_AdvSimd_Abs, NI_VectorT128_Abs, NI_AdvSimd_Abs, NI_VectorT128_Abs, NI_AdvSimd_Abs, NI_VectorT128_Abs, NI_AdvSimd_Arm64_Abs, NI_VectorT128_Abs, NI_AdvSimd_Abs, NI_AdvSimd_Arm64_Abs}, SimdAsHWIntrinsicFlag::None)
SIMD_AS_HWINTRINSIC_ID(VectorT128, AndNot, 2, {NI_AdvSimd_BitwiseClear, NI_AdvSimd_BitwiseClear, NI_AdvSimd_BitwiseClear, NI_AdvSimd_BitwiseClear, NI_AdvSimd_BitwiseClear, NI_AdvSimd_BitwiseClear, NI_AdvSimd_BitwiseClear, NI_AdvSimd_BitwiseClear, NI_AdvSimd_BitwiseClear, NI_AdvSimd_BitwiseClear}, SimdAsHWIntrinsicFlag::None)
SIMD_AS_HWINTRINSIC_ID(VectorT128, As, 1, {NI_VectorT128_As, NI_VectorT128_As, NI_VectorT128_As, NI_VectorT128_As, NI_VectorT128_As, NI_VectorT128_As, NI_VectorT128_As, NI_VectorT128_As, NI_VectorT128_As, NI_VectorT128_As}, SimdAsHWIntrinsicFlag::None)
SIMD_AS_HWINTRINSIC_ID(VectorT128, Ceiling, 1, {NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_AdvSimd_Ceiling, NI_AdvSimd_Arm64_Ceiling}, SimdAsHWIntrinsicFlag::None)
SIMD_AS_HWINTRINSIC_ID(VectorT128, ConditionalSelect, 3, {NI_VectorT128_ConditionalSelect, NI_VectorT128_ConditionalSelect, NI_VectorT128_ConditionalSelect, NI_VectorT128_ConditionalSelect, NI_VectorT128_ConditionalSelect, NI_VectorT128_ConditionalSelect, NI_VectorT128_ConditionalSelect, NI_VectorT128_ConditionalSelect, NI_VectorT128_ConditionalSelect, NI_VectorT128_ConditionalSelect}, SimdAsHWIntrinsicFlag::None)
SIMD_AS_HWINTRINSIC_NM(VectorT128, CreateBroadcast, ".ctor", 2, {NI_VectorT128_CreateBroadcast, NI_VectorT128_CreateBroadcast, NI_VectorT128_CreateBroadcast, NI_VectorT128_CreateBroadcast, NI_VectorT128_CreateBroadcast, NI_VectorT128_CreateBroadcast, NI_VectorT128_CreateBroadcast, NI_VectorT128_CreateBroadcast, NI_VectorT128_CreateBroadcast, NI_VectorT128_CreateBroadcast}, SimdAsHWIntrinsicFlag::InstanceMethod)
Expand Down
2 changes: 2 additions & 0 deletions src/coreclr/jit/simdashwintrinsiclistxarch.h
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ SIMD_AS_HWINTRINSIC_ID(Vector4, SquareRoot,
// Vector<T> Intrinsics
SIMD_AS_HWINTRINSIC_ID(VectorT128, Abs, 1, {NI_VectorT128_Abs, NI_VectorT128_Abs, NI_VectorT128_Abs, NI_VectorT128_Abs, NI_VectorT128_Abs, NI_VectorT128_Abs, NI_VectorT128_Abs, NI_VectorT128_Abs, NI_VectorT128_Abs, NI_VectorT128_Abs}, SimdAsHWIntrinsicFlag::None)
SIMD_AS_HWINTRINSIC_ID(VectorT128, AndNot, 2, {NI_SSE2_AndNot, NI_SSE2_AndNot, NI_SSE2_AndNot, NI_SSE2_AndNot, NI_SSE2_AndNot, NI_SSE2_AndNot, NI_SSE2_AndNot, NI_SSE2_AndNot, NI_SSE_AndNot, NI_SSE2_AndNot}, SimdAsHWIntrinsicFlag::NeedsOperandsSwapped)
SIMD_AS_HWINTRINSIC_ID(VectorT128, As, 1, {NI_VectorT128_As, NI_VectorT128_As, NI_VectorT128_As, NI_VectorT128_As, NI_VectorT128_As, NI_VectorT128_As, NI_VectorT128_As, NI_VectorT128_As, NI_VectorT128_As, NI_VectorT128_As}, SimdAsHWIntrinsicFlag::None)
SIMD_AS_HWINTRINSIC_ID(VectorT128, Ceiling, 1, {NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_SSE41_Ceiling, NI_SSE41_Ceiling}, SimdAsHWIntrinsicFlag::None)
SIMD_AS_HWINTRINSIC_ID(VectorT128, ConditionalSelect, 3, {NI_VectorT128_ConditionalSelect, NI_VectorT128_ConditionalSelect, NI_VectorT128_ConditionalSelect, NI_VectorT128_ConditionalSelect, NI_VectorT128_ConditionalSelect, NI_VectorT128_ConditionalSelect, NI_VectorT128_ConditionalSelect, NI_VectorT128_ConditionalSelect, NI_VectorT128_ConditionalSelect, NI_VectorT128_ConditionalSelect}, SimdAsHWIntrinsicFlag::None)
SIMD_AS_HWINTRINSIC_NM(VectorT128, CreateBroadcast, ".ctor", 2, {NI_VectorT128_CreateBroadcast, NI_VectorT128_CreateBroadcast, NI_VectorT128_CreateBroadcast, NI_VectorT128_CreateBroadcast, NI_VectorT128_CreateBroadcast, NI_VectorT128_CreateBroadcast, NI_VectorT128_CreateBroadcast, NI_VectorT128_CreateBroadcast, NI_VectorT128_CreateBroadcast, NI_VectorT128_CreateBroadcast}, SimdAsHWIntrinsicFlag::InstanceMethod)
Expand Down Expand Up @@ -138,6 +139,7 @@ SIMD_AS_HWINTRINSIC_ID(VectorT128, SquareRoot,
// Vector<T> Intrinsics
SIMD_AS_HWINTRINSIC_ID(VectorT256, Abs, 1, {NI_AVX2_Abs, NI_VectorT256_Abs, NI_AVX2_Abs, NI_VectorT256_Abs, NI_AVX2_Abs, NI_VectorT256_Abs, NI_VectorT256_Abs, NI_VectorT256_Abs, NI_VectorT256_Abs, NI_VectorT256_Abs}, SimdAsHWIntrinsicFlag::None)
SIMD_AS_HWINTRINSIC_ID(VectorT256, AndNot, 2, {NI_AVX2_AndNot, NI_AVX2_AndNot, NI_AVX2_AndNot, NI_AVX2_AndNot, NI_AVX2_AndNot, NI_AVX2_AndNot, NI_AVX2_AndNot, NI_AVX2_AndNot, NI_AVX_AndNot, NI_AVX_AndNot}, SimdAsHWIntrinsicFlag::NeedsOperandsSwapped)
SIMD_AS_HWINTRINSIC_ID(VectorT256, As, 1, {NI_VectorT256_As, NI_VectorT256_As, NI_VectorT256_As, NI_VectorT256_As, NI_VectorT256_As, NI_VectorT256_As, NI_VectorT256_As, NI_VectorT256_As, NI_VectorT256_As, NI_VectorT256_As}, SimdAsHWIntrinsicFlag::None)
SIMD_AS_HWINTRINSIC_ID(VectorT256, Ceiling, 1, {NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_AVX_Ceiling, NI_AVX_Ceiling}, SimdAsHWIntrinsicFlag::None)
SIMD_AS_HWINTRINSIC_ID(VectorT256, ConditionalSelect, 3, {NI_VectorT256_ConditionalSelect, NI_VectorT256_ConditionalSelect, NI_VectorT256_ConditionalSelect, NI_VectorT256_ConditionalSelect, NI_VectorT256_ConditionalSelect, NI_VectorT256_ConditionalSelect, NI_VectorT256_ConditionalSelect, NI_VectorT256_ConditionalSelect, NI_VectorT256_ConditionalSelect, NI_VectorT256_ConditionalSelect}, SimdAsHWIntrinsicFlag::None)
SIMD_AS_HWINTRINSIC_NM(VectorT256, CreateBroadcast, ".ctor", 2, {NI_VectorT256_CreateBroadcast, NI_VectorT256_CreateBroadcast, NI_VectorT256_CreateBroadcast, NI_VectorT256_CreateBroadcast, NI_VectorT256_CreateBroadcast, NI_VectorT256_CreateBroadcast, NI_VectorT256_CreateBroadcast, NI_VectorT256_CreateBroadcast, NI_VectorT256_CreateBroadcast, NI_VectorT256_CreateBroadcast}, SimdAsHWIntrinsicFlag::InstanceMethod)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,7 @@ public static partial class Vector
public static System.Numerics.Vector<T> Abs<T>(System.Numerics.Vector<T> value) where T : struct { throw null; }
public static System.Numerics.Vector<T> Add<T>(System.Numerics.Vector<T> left, System.Numerics.Vector<T> right) where T : struct { throw null; }
public static System.Numerics.Vector<T> AndNot<T>(System.Numerics.Vector<T> left, System.Numerics.Vector<T> right) where T : struct { throw null; }
public static System.Numerics.Vector<TTo> As<TFrom, TTo>(this System.Numerics.Vector<TFrom> vector) where TFrom : struct where TTo : struct { throw null; }
public static System.Numerics.Vector<System.Byte> AsVectorByte<T>(System.Numerics.Vector<T> value) where T : struct { throw null; }
public static System.Numerics.Vector<System.Double> AsVectorDouble<T>(System.Numerics.Vector<T> value) where T : struct { throw null; }
public static System.Numerics.Vector<System.Int16> AsVectorInt16<T>(System.Numerics.Vector<T> value) where T : struct { throw null; }
Expand Down
90 changes: 90 additions & 0 deletions src/libraries/System.Numerics.Vectors/tests/GenericVectorTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3047,6 +3047,96 @@ public void NarrowDouble()

#endregion Narrow / Widen

#region Explicit Cast/As
[Fact]
public void TestCastByteToInt() => TestCastToInt<byte>();

[Fact]
public void TestCastSByteToInt() => TestCastToInt<sbyte>();

[Fact]
public void TestCastInt16ToInt() => TestCastToInt<short>();

[Fact]
public void TestCastUInt16ToInt() => TestCastToInt<ushort>();

[Fact]
public void TestCastInt32ToInt() => TestCastToInt<int>();

[Fact]
public void TestCastUInt32ToInt() => TestCastToInt<uint>();

[Fact]
public void TestCastInt64ToInt() => TestCastToInt<long>();

[Fact]
public void TestCastUInt64ToInt() => TestCastToInt<ulong>();

[Fact]
public void TestCastSingleToInt() => TestCastToInt<float>();

[Fact]
public void TestCastDoubleToInt() => TestCastToInt<double>();

private unsafe void TestCastToInt<T>() where T : unmanaged
{
T[] values = GenerateRandomValuesForVector<T>();
Vector<T> vector1 = new Vector<T>(values);
Vector<int> vector2 = (Vector<int>)vector1;

var vector1Bytes = new byte[sizeof(T) * Vector<T>.Count];
vector1.CopyTo(vector1Bytes);
var vector2Bytes = new byte[sizeof(int) * Vector<int>.Count];
vector2.CopyTo(vector2Bytes);

Assert.Equal(vector1Bytes, vector2Bytes);
}

[Fact]
public void TestAsIntToByte() => TestAs<int, byte>();

[Fact]
public void TestAsIntToSByte() => TestAs<int, sbyte>();

[Fact]
public void TestAsIntToInt16() => TestAs<int, short>();

[Fact]
public void TestAsIntToUInt16() => TestAs<int, ushort>();

[Fact]
public void TestAsIntToInt32() => TestAs<int, int>();

[Fact]
public void TestAsIntToUInt32() => TestAs<int, uint>();

[Fact]
public void TestAsIntToInt64() => TestAs<int, long>();

[Fact]
public void TestAsIntToUInt64() => TestAs<int, ulong>();

[Fact]
public void TestAsIntToSingle() => TestAs<int, float>();

[Fact]
public void TestAsIntToDouble() => TestAs<int, double>();

private unsafe void TestAs<TFrom, TTo>() where TFrom : unmanaged where TTo : unmanaged
{
TFrom[] values = GenerateRandomValuesForVector<TFrom>();
Vector<TFrom> vector1 = new Vector<TFrom>(values);
Vector<TTo> vector2 = vector1.As<TFrom, TTo>();

var vector1Bytes = new byte[sizeof(TFrom) * Vector<TFrom>.Count];
vector1.CopyTo(vector1Bytes);
var vector2Bytes = new byte[sizeof(TTo) * Vector<TTo>.Count];
vector2.CopyTo(vector2Bytes);

Assert.Equal(vector1Bytes, vector2Bytes);
}
#endregion

#region Helper Methods
private static void AssertEqual<T>(T expected, T actual, string operation, int precision = -1) where T : IEquatable<T>
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -344,5 +344,19 @@ public void ToVectorDoubleTest()
Vector<bool> vector = default;
Assert.Throws<NotSupportedException>(() => (Vector<double>)vector);
}

[Fact]
public void AsFromTest()
{
Vector<bool> vector = default;
Assert.Throws<NotSupportedException>(() => vector.As<bool, int>());
}

[Fact]
public void AsToTest()
{
Vector<int> vector = default;
Assert.Throws<NotSupportedException>(() => vector.As<int, bool>());
}
}
}
23 changes: 23 additions & 0 deletions src/libraries/System.Private.CoreLib/src/System/Numerics/Vector.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using Internal.Runtime.CompilerServices;

namespace System.Numerics
{
Expand Down Expand Up @@ -1431,5 +1432,27 @@ internal static void ThrowInsufficientNumberOfElementsException(int requiredElem
{
throw new IndexOutOfRangeException(SR.Format(SR.Arg_InsufficientNumberOfElements, requiredElementCount, "values"));
}

/// <summary>
/// Reinterprets a <see cref="Vector{T}"/> as a <see cref="Vector{T}"/> of new type.
/// </summary>
/// <typeparam name="TFrom">The type of the input vector.</typeparam>
/// <typeparam name="TTo">The type to reinterpret the vector as.</typeparam>
/// <param name="vector">The vector to reinterpret.</param>
/// <returns><paramref name="vector"/> reinterpreted as a new <see cref="Vector{T}"/>.</returns>
/// <exception cref="NotSupportedException">
/// The type of <typeparamref name="TFrom"/> or <typeparamref name="TTo"/> is not supported.
/// </exception>
[Intrinsic]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector<TTo> As<TFrom, TTo>(this Vector<TFrom> vector)
where TFrom : struct
where TTo : struct
{
ThrowHelper.ThrowForUnsupportedVectorBaseType<TFrom>();
ThrowHelper.ThrowForUnsupportedVectorBaseType<TTo>();

return Unsafe.As<Vector<TFrom>, Vector<TTo>>(ref vector);
}
}
}

0 comments on commit fabaed2

Please sign in to comment.