NOTE: This is currently more of an exploration for API look and feel than an actual proposal.
Recently, the design document for .NET Platform Dependent Intrinsics was published (https://github.com/dotnet/designs/blob/master/accepted/platform-intrinsics.md).
One of the things that still needs to be determined is the exact "look and feel" of the exposed APIs. Given the requirements put forth in the design doc, I explored what exposing the x86 SSE intrinsics might look like.
I believe we need a minimum of 3 attributes:
Intrinsic identifies a method as exposing an intrinsicLiteral identifiers a parameter that is required to be a literal (there are a few instructions that require this)modreq literal to be emitted and would have compiler supportsSystem.Runtime.CompilerServices rather than S.R.CS.IntrinsicsAlignment enforces an alignment for a struct.System.Runtime.InteropServices where it makes more senseFor SSE, there are 4 different register sizes: 64, 128, 256, and 512.
IsSupported property so the user can determine whether the CPU supports that register size (for example, this can indicate support for https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm256_cvtss&expand=1809).Simd128<float> as a field, and instead would declare float x, y, z, w and would explicitly call Load and Store to get a Simd128<float>The SSE static class exposes all the instructions supported by the SSE CPUID bit flag:
Simd128<float> and just treat the parameter differently.```C#
namespace System.Runtime.CompilerServices.Intrinsics
{
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = false)]
internal sealed class IntrinsicAttribute : Attribute
{
// Indicates that a method is an intrinsic
// Optional. Allow users to specify useful metadata such as the corresponding C/C++ intrinsic or hardware instruction
}
[AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false, Inherited = false)]
public sealed class LiteralAttribute : Attribute
{
// Indicates that a parameter is required to be literal.
// Should be emitted as a `modreq literal`
}
[AttributeUsage(AttributeTargets.Struct, AllowMultiple = false, Inherited = false)]
public sealed class AlignmentAttribute : Attribute
{
// Indicates the required alignment of a type.
public AlignmentAttribute(int value) { }
}
[Alignment(8)]
public struct Simd64<T>
{
// This should be recognized by the JIT as a constant
// This indicates whether the hardware supports holding values of T
public static bool IsSupported { get; }
}
[Alignment(16)]
public struct Simd128<T>
{
// This should be recognized by the JIT as a constant
// This indicates whether the hardware supports holding values of T
public static bool IsSupported { get; }
}
[Alignment(32)]
public struct Simd256<T>
{
// This should be recognized by the JIT as a constant
// This indicates whether the hardware supports holding values of T
public static bool IsSupported { get; }
}
[Alignment(64)]
public struct Simd512<T>
{
// This should be recognized by the JIT as a constant
// This indicates whether the hardware supports holding values of T
public static bool IsSupported { get; }
}
namespace x86
{
public static unsafe class SSE
{
// This should be recognized by the JIT as a constant
// This indicates whether the hardware supports this instruction set.
public static bool IsSupported { get; }
#region Data Transfer Instructions
[Intrinsic] // MOVAPS, _mm_load_ps
public static extern Simd128<float> LoadAligned(float* address);
[Intrinsic] // MOVAPS, _mm_store_ps
public static extern void StoreAligned(float* address, Simd128<float> value);
[Intrinsic] // MOVUPS, _mm_loadu_ps
public static extern Simd128<float> LoadUnaligned(float* address);
[Intrinsic] // MOVUPS, _mm_storeu_ps
public static extern void StoreUnaligned(float* address, Simd128<float> value);
[Intrinsic] // MOVHPS, _mm_loadh_pi
public static extern Simd128<float> LoadHigh(Simd128<float> value, float* address);
[Intrinsic] // MOVHPS, _mm_storeh_pi
public static extern void StoreHigh(float* address, Simd128<float> value);
[Intrinsic] // MOVHLPS, _mm_movehl_ps
public static extern Simd128<float> MoveHighLow(Simd128<float> a, Simd128<float> b);
[Intrinsic] // MOVLPS, _mm_loadl_pi
public static extern Simd128<float> LoadLow(Simd128<float> value, float* address);
[Intrinsic] // MOVLPS, _mm_storel_pi
public static extern void StoreLow(float* address, Simd128<float> value);
[Intrinsic] // MOVLHPS, _mm_movelh_ps
public static extern Simd128<float> MoveLowHigh(Simd128<float> a, Simd128<float> b);
[Intrinsic] // MOVMSKPS, _mm_movemask_ps
public static extern int MoveMask(Simd128<float> value);
[Intrinsic] // MOVSS, _mm_cvtss_f32
public static extern float ToSingle(Simd128<float> value);
[Intrinsic] // MOVSS, _mm256_cvtss_f32
public static extern float ToSingle(Simd256<float> value);
[Intrinsic] // MOVSS, _mm512_cvtss_f32
public static extern float ToSingle(Simd512<float> value);
[Intrinsic] // MOVSS, _mm_load_ss
public static extern Simd128<float> Load(float* address);
[Intrinsic] // MOVSS, _mm_move_ss
public static extern Simd128<float> Move(Simd128<float> a, Simd128<float> b);
[Intrinsic] // MOVSS, _mm_store_ss
public static extern void Store(float* address, Simd128<float> value);
#endregion
#region Arithmetic Instructions
[Intrinsic] // ADDPS, _mm_add_ps
public static extern Simd128<float> Add(Simd128<float> a, Simd128<float> b);
[Intrinsic] // ADDSS, _mm_add_ss
public static extern Simd128<float> AddScalar(Simd128<float> a, Simd128<float> b);
[Intrinsic] // SUBPS, _mm_sub_ps
public static extern Simd128<float> Subtract(Simd128<float> a, Simd128<float> b);
[Intrinsic] // SUBSS, _mm_sub_ss
public static extern Simd128<float> SubtractScalar(Simd128<float> a, Simd128<float> b);
[Intrinsic] // MULPS, _mm_mul_ps
public static extern Simd128<float> Multiply(Simd128<float> a, Simd128<float> b);
[Intrinsic] // MULSS, _mm_mul_ss
public static extern Simd128<float> MultiplyScalar(Simd128<float> a, Simd128<float> b);
[Intrinsic] // DIVPS, _mm_div_ps
public static extern Simd128<float> Divide(Simd128<float> a, Simd128<float> b);
[Intrinsic] // DIVSS, _mm_div_ss
public static extern Simd128<float> DivideScalar(Simd128<float> a, Simd128<float> b);
[Intrinsic] // RCPPS, _mm_rcp_ps
public static extern Simd128<float> Reciprocal(Simd128<float> value);
[Intrinsic] // RCPSS, _mm_rcp_ss
public static extern Simd128<float> ReciprocalScalar(Simd128<float> value);
[Intrinsic] // SQRTPS, _mm_sqrt_ps
public static extern Simd128<float> Sqrt(Simd128<float> value);
[Intrinsic] // SQRTSS, _mm_sqrt_ss
public static extern Simd128<float> SqrtScalar(Simd128<float> value);
[Intrinsic] // RSQRTPS, _mm_rsqrt_ps
public static extern Simd128<float> ReciprocalSqrt(Simd128<float> value);
[Intrinsic] // RSQRTSS, _mm_rsqrt_ss
public static extern Simd128<float> ReciprocalSqrtScalar(Simd128<float> value);
[Intrinsic] // MAXPS, _mm_max_ps
public static extern Simd128<float> Max(Simd128<float> a, Simd128<float> b);
[Intrinsic] // MAXSS, _mm_max_ss
public static extern Simd128<float> MaxScalar(Simd128<float> a, Simd128<float> b);
[Intrinsic] // MINPS, _mm_min_ps
public static extern Simd128<float> Min(Simd128<float> a, Simd128<float> b);
[Intrinsic] // MINSS, _mm_min_ss
public static extern Simd128<float> MinScalar(Simd128<float> a, Simd128<float> b);
#endregion
#region Comparison Instructions
[Intrinsic] // CMPPS, _mm_cmpeq_ps
public static extern Simd128<float> CompareEqual(Simd128<float> a, Simd128<float> b);
[Intrinsic] // CMPPS, _mm_cmpge_ps
public static extern Simd128<float> CompareGreaterOrEqual(Simd128<float> a, Simd128<float> b);
[Intrinsic] // CMPPS, _mm_cmpgt_ps
public static extern Simd128<float> CompareGreater(Simd128<float> a, Simd128<float> b);
[Intrinsic] // CMPPS, _mm_cmple_ps
public static extern Simd128<float> CompareLessOrEqual(Simd128<float> a, Simd128<float> b);
[Intrinsic] // CMPPS, _mm_cmplt_ps
public static extern Simd128<float> CompareLess(Simd128<float> a, Simd128<float> b);
[Intrinsic] // CMPPS, _mm_cmpneq_ps
public static extern Simd128<float> CompareNotEqual(Simd128<float> a, Simd128<float> b);
[Intrinsic] // CMPPS, _mm_cmpnge_ps
public static extern Simd128<float> CompareNotGreaterOrEqual(Simd128<float> a, Simd128<float> b);
[Intrinsic] // CMPPS, _mm_cmpngt_ps
public static extern Simd128<float> CompareNotGreater(Simd128<float> a, Simd128<float> b);
[Intrinsic] // CMPPS, _mm_cmpnle_ps
public static extern Simd128<float> CompareNotLessOrEqual(Simd128<float> a, Simd128<float> b);
[Intrinsic] // CMPPS, _mm_cmpnlt_ps
public static extern Simd128<float> CompareNotLess(Simd128<float> a, Simd128<float> b);
[Intrinsic] // CMPPS, _mm_cmpord_ps
public static extern Simd128<float> CompareOrdered(Simd128<float> a, Simd128<float> b);
[Intrinsic] // CMPPS, _mm_cmpunord_ps
public static extern Simd128<float> CompareUnordered(Simd128<float> a, Simd128<float> b);
[Intrinsic] // CMPSS, _mm_cmpeq_ss
public static extern Simd128<float> CompareEqualScalar(Simd128<float> a, Simd128<float> b);
[Intrinsic] // CMPSS, _mm_cmpge_ss
public static extern Simd128<float> CompareGreaterOrEqualScalar(Simd128<float> a, Simd128<float> b);
[Intrinsic] // CMPSS, _mm_cmpgt_ss
public static extern Simd128<float> CompareGreaterScalar(Simd128<float> a, Simd128<float> b);
[Intrinsic] // CMPSS, _mm_cmple_ss
public static extern Simd128<float> CompareLessOrEqualScalar(Simd128<float> a, Simd128<float> b);
[Intrinsic] // CMPSS, _mm_cmplt_ss
public static extern Simd128<float> CompareLessScalar(Simd128<float> a, Simd128<float> b);
[Intrinsic] // CMPSS, _mm_cmpneq_ss
public static extern Simd128<float> CompareNotEqualScalar(Simd128<float> a, Simd128<float> b);
[Intrinsic] // CMPSS, _mm_cmpnge_ss
public static extern Simd128<float> CompareNotGreaterOrEqualScalar(Simd128<float> a, Simd128<float> b);
[Intrinsic] // CMPSS, _mm_cmpngt_ss
public static extern Simd128<float> CompareNotGreaterScalar(Simd128<float> a, Simd128<float> b);
[Intrinsic] // CMPSS, _mm_cmpnle_ss
public static extern Simd128<float> CompareNotLessOrEqualScalar(Simd128<float> a, Simd128<float> b);
[Intrinsic] // CMPSS, _mm_cmpnlt_ss
public static extern Simd128<float> CompareNotLessScalar(Simd128<float> a, Simd128<float> b);
[Intrinsic] // CMPSS, _mm_cmpord_ss
public static extern Simd128<float> CompareOrderedScalar(Simd128<float> a, Simd128<float> b);
[Intrinsic] // CMPSS, _mm_cmpunord_ss
public static extern Simd128<float> CompareUnorderedScalar(Simd128<float> a, Simd128<float> b);
[Intrinsic] // COMISS, _mm_comieq_ss
public static extern bool OrderedCompareEqualScalar(Simd128<float> a, Simd128<float> b);
[Intrinsic] // COMISS, _mm_comige_ss
public static extern bool OrderedCompareGreaterOrEqualScalar(Simd128<float> a, Simd128<float> b);
[Intrinsic] // COMISS, _mm_comigt_ss
public static extern bool OrderedCompareGreaterScalar(Simd128<float> a, Simd128<float> b);
[Intrinsic] // COMISS, _mm_comile_ss
public static extern bool OrderedCompareLessOrEqualScalar(Simd128<float> a, Simd128<float> b);
[Intrinsic] // COMISS, _mm_comilt_ss
public static extern bool OrderedCompareLessScalar(Simd128<float> a, Simd128<float> b);
[Intrinsic] // COMISS, _mm_comineq_ss
public static extern bool OrderedCompareNotEqualScalar(Simd128<float> a, Simd128<float> b);
[Intrinsic] // UCOMISS, _mm_ucomieq_ss
public static extern bool UnorderedCompareEqualScalar(Simd128<float> a, Simd128<float> b);
[Intrinsic] // UCOMISS, _mm_ucomige_ss
public static extern bool UnorderedCompareGreaterOrEqualScalar(Simd128<float> a, Simd128<float> b);
[Intrinsic] // UCOMISS, _mm_ucomigt_ss
public static extern bool UnorderedCompareGreaterScalar(Simd128<float> a, Simd128<float> b);
[Intrinsic] // UCOMISS, _mm_ucomile_ss
public static extern bool UnorderedCompareLessOrEqualScalar(Simd128<float> a, Simd128<float> b);
[Intrinsic] // UCOMISS, _mm_ucomilt_ss
public static extern bool UnorderedCompareLessScalar(Simd128<float> a, Simd128<float> b);
[Intrinsic] // UCOMISS, _mm_ucomineq_ss
public static extern bool UnorderedCompareNotEqualScalar(Simd128<float> a, Simd128<float> b);
#endregion
#region Logical Instructions
[Intrinsic] // ANDPS, _mm_and_ps
public static extern Simd128<float> And(Simd128<float> a, Simd128<float> b);
[Intrinsic] // ANDNPS, _mm_andnot_ps
public static extern Simd128<float> AndNot(Simd128<float> a, Simd128<float> b);
[Intrinsic] // ORPS, _mm_or_ps
public static extern Simd128<float> Or(Simd128<float> a, Simd128<float> b);
[Intrinsic] // XORPS, _mm_xor_ps
public static extern Simd128<float> XOr(Simd128<float> a, Simd128<float> b);
#endregion
#region Shuffle and Unpack Instructions
[Intrinsic] // SHUFPS, _mm_shuffle_ps
public static extern Simd128<float> Shuffle(Simd128<float> a, Simd128<float> b, [Literal] byte imm8);
[Intrinsic] // UNPCKHPS, _mm_unpackhi_ps
public static extern Simd128<float> UnpackHigh(Simd128<float> a, Simd128<float> b);
[Intrinsic] // UNPCKLPS, _mm_unpacklo_ps
public static extern Simd128<float> UnpackLow(Simd128<float> a, Simd128<float> b);
#endregion
#region Conversion Instructions
[Intrinsic] // CVTPI2PS, _mm_cvt_pi2ps, _mm_cvtpi32_ps
public static extern Simd128<float> ToPackedSingle(Simd128<float> a, Simd64<int> b);
[Intrinsic] // CVTSI2SS, _mm_cvt_si2ss, _mm_cvtsi32_ss
public static extern Simd128<float> ToPackedSingle(Simd128<float> a, int b);
[Intrinsic] // CVTSI2SS, _mm_cvtsi64_ss
public static extern Simd128<float> ToPackedSingle(Simd128<float> a, long b);
[Intrinsic] // CVTPS2PI, _mm_cvt_ps2pi, _mm_cvtps_pi32
public static extern Simd64<int> ToPackedInt32(Simd128<float> value);
[Intrinsic] // CVTTPS2PI, _mm_cvtt_ps2pi, _mm_cvttps_pi32
public static extern Simd64<int> ToPackedInt32Truncated(Simd128<float> value);
[Intrinsic] // CVTSS2SI, _mm_cvt_ss2si, _mm_cvtss_si32
public static extern int ToInt32(Simd128<float> value);
[Intrinsic] // CVTSS2SI, _mm_cvtss_si64
public static extern long ToInt64(Simd128<float> value);
[Intrinsic] // CVTTSS2SI, _mm_cvvt_ss2si, _mm_cvttss_si32
public static extern int ToInt32Truncated(Simd128<float> value);
[Intrinsic] // CVTTSS2SI, _mm_cvttss_si64
public static extern long ToInt64Truncated(Simd128<float> value);
#endregion
#region MXCSR State Management Instructions
[Intrinsic] // LDMXCSR, _mm_setcsr
public static extern void SetControlStatus(uint a);
[Intrinsic] // STMXCSR, _mm_getcsr
public static extern uint GetControlStatus();
#endregion
#region 64-bit Integer Instructions
[Intrinsic] // PAVGB, _mm_avg_pu8, _m_pavgb
public static extern Simd64<byte> Average(Simd64<byte> a, Simd64<byte> b);
[Intrinsic] // PAVGW, _mm_avg_pu16, _m_pavgw
public static extern Simd64<ushort> Average(Simd64<ushort> a, Simd64<ushort> b);
[Intrinsic] // PEXTRW, _mm_extract_pi16, _m_pextrw
public static extern short Extract(Simd64<short> a, [Literal] byte imm8);
[Intrinsic] // PINSRW, _mm_insert_pi16, _m_pinsrw
public static extern Simd64<short> Insert(Simd64<short> a, short i, [Literal] byte imm8);
[Intrinsic] // PMAXUB, _mm_max_pu8, _m_pmaxub
public static extern Simd64<byte> Max(Simd64<byte> a, Simd64<byte> b);
[Intrinsic] // PMAXSW, _mm_max_pu16, _m_pmaxuw
public static extern Simd64<ushort> Max(Simd64<ushort> a, Simd64<ushort> b);
[Intrinsic] // PMINUB, _mm_min_pu8, _m_pminub
public static extern Simd64<byte> Min(Simd64<byte> a, Simd64<byte> b);
[Intrinsic] // PMINSW, _mm_min_pu16, _m_pminuw
public static extern Simd64<ushort> Min(Simd64<ushort> a, Simd64<ushort> b);
[Intrinsic] // PMOVMSKB, _mm_movemask_pi8, _m_pmovmskb
public static extern int MoveMask(Simd64<byte> value);
[Intrinsic] // PMULHUWm _mm_mulhi_pu16, _m_pmulhuw
public static extern Simd64<ushort> MultiplyHigh(Simd64<ushort> a, Simd64<ushort> b);
[Intrinsic] // PSADBW, _mm_sad_pu8, _m_psadbw
public static extern Simd64<ushort> AbsoluteSumOfDifferences(Simd64<byte> a, Simd64<byte> b);
[Intrinsic] // PSHUFW, _mm_shuffle_pi16, _m_pshufw
public static extern Simd64<short> Shuffle(Simd64<short> value, [Literal] byte imm8);
#endregion
#region Cacheability Control, Prefetch, and Instruction Ordering Instructions
[Intrinsic] // MASKMOVQ, _mm_maskmove_si64, _m_maskmovq
public static extern void MaskMove(Simd64<byte> a, Simd64<byte> mask, byte* address);
[Intrinsic] // MOVNTQ, _mm_stream_pi
public static extern void Stream(long* address, Simd64<byte> value);
[Intrinsic] // MOVNTPS, _mm_stream_ps
public static extern void Stream(float* address, Simd128<float> value);
[Intrinsic] // PREFETCHNTA, PREFETCH0, PREFETCH1, PREFETCH2, _mm_prefetch
public static extern void Prefetch(byte* p, int i);
[Intrinsic] // SFENCE, _mm_sfence
public static extern void StoreFence();
#endregion
}
}
}
```
FYI. @mellinoe, @russellhadley.
Just an exploration with some notes
Intrinsicidentifies a method as exposing an intrinsic
The Intrinsic attribute should be internal implementation detail. It should not be part of the public surface.
That's HUUUGE for all performance critical code. I would like to compliment DotNet team for this decision.
I believe we need a minimum of 3 attributes:
Literal identifiers a parameter that is required to be a literal (there are a few instructions that require this)
Ideally this would cause modreq literal to be emitted and would have compiler supports
This could probably go in System.Runtime.CompilerServices rather than S.R.CS.Intrinsics
IMO it would be wrong to use attribute to enforce syntactic and semantic requirements in the programming language if there is a simple syntactic alternative (see https://github.com/dotnet/csharplang/issues/744 and discussion in https://github.com/dotnet/corefx/issues/16835). In any statically typed language it is a type system which is used in a first place to enforce type safety and what is more important it is used in a way which aligns well with language style what makes developers life easier.
Implementation of const parameter feature in Roslyn should be trivial and would improve C# expressiveness in a way consistent with current syntax. It would be a natural syntactic extension of accepted readonly ref parameters language feature to compile time constants.
Furthermore parameter required for several intrinsics is a compile time const integer. In C# it does not necessarily mean that it has to be literal expression as a constexpr provides a more flexible equivalent and is accepted in const declaration syntax.
From type safety perspective it would be very hard to prove that syntax enforcement via attribute is sound, correct and verifiable (it is not impossible but significantly harder as a prerequisite to a proof one would have to prove soundness of underlying attribute type system including syntactic quirks like LiteralAttribute). AFAIR all features of C# and CIL have been formally proven to be sound and verifiable For examples see:
IMO it would be wrong to use attribute to enforce syntactic and semantic requirements in the programming language if there is a simple syntactic alternative
To be clear, if the language did end up supporting constexpr it would likely end up emitted as an attribute as well. You need to be able to carry that metadata around in the post-compilation assembly so that consumers know any restrictions as well as being able to detect that the type/method/field supports constexpr. Carrying around that metadata is restricted to the ways exposed by the IL to do so which pretty much boils down to modopt, modreq, and general attributes.
'modreq' and modopt are special attributes that attach to a method parameter and become part of the method signature. modreq requires that the compiler understand the meaning of the attached 'keyword' or refuse to compile.
The C# compiler already uses all three of these attribute types to attach various metadata for features it provides (readonly ref uses modreq extensively).
It may additionally be worthwhile to note that constants in C# are really just IL literals. They exist for the compiler, purely in metadata, and are never directly processed by the JIT. Instead, any use by the compiler is directly inlined to the generated IL.
For constexpr, the compiler would likely do automatic folding (as per the feature) and emit literals inline as well (thereby dropping any method calls completely). So, a constexpr that returns a primitive type can be treated as literal.
In the end, if the language decide to support a modreq for this, it would end up being their decision on how it was implemented and how it integrates with other features. They would naturally take into account the cost and any future plans in related areas (such as constexpr). If they instead decided to not do this, there would likely need to be an analyzer to help enforce users are doing the right thing.
To be clear, if the language did end up supporting
constexprit would likely end up emitted as an attribute as well
Carrying around that metadata is restricted to the ways exposed by the IL to do so which pretty much boils down to
modopt,modreq, and general attributes.
I think we are discussing two different problems:
Ad. 1.
Both attribute usage and const parameter proposal were authored by me (issue dotnet/runtime#20510) but I am not married to any of them. I would like to see best and most intuitive and concise syntax for this feature. Let's see the consequences of either choice from user perspective by comparing it on examples:
```C#
public static Simd256 Shuffle(Simd256 value, [LiteralAttribute()] int mask);
public static Simd256 Shuffle(Simd256 value, const int mask);
Simd256 a, b;
int x = Foo();
// .... initialization code
b = Simd256.Shuffle(a, x); // compile error bcs x was declared with LiteralAttribute
b = Simd256.Shuffle(a, b, const x); // compile error bcs x is not compile time constant
```
I prefer second solution as it is clean and aligns well with C# syntaxt.
Ad. 2.
For me it is just secondary to C# syntax problem.
My primary point is that [LiteralAttribute] int mask could be supported today with very little in the way of the language (it essentially boils down to a compiler only change to understand the restrictions around the method).
While const int mask, on the other hand, requires significant more discussion, design, implementation, etc (as it becomes a feature of the language, rather than something simply understood by the compiler).
Additionally, you can do LiteralAttribute today and later change that to be const in the language if/when constexpr gets implemented by the language.
I would, personally, rather see this feature come to light without it being delayed by lengthy language design discussions and/or by being blocked by a language feature that is mostly orthogonal.
Additionally, you can do LiteralAttribute today and later change that to be const in the language if/when constexpr gets implemented by the language.
I would, personally, rather see this feature come to light without it being delayed by lengthy language design discussions and/or by being blocked by a language feature that is mostly orthogonal.
I agree - I would prefer to have it as fast as possible and if it is possible to get it faster without language syntax changes than this is the way to go.
Is it possible to explore x86 SIMD API which comprises much more than SSE or the title by intention limits discussion to SSE extensions only?
@4creators, I created this post mostly as just an exploration of what such a thing might look like and to help get discussions going (because I am actively interested in this).
Keeping in mind that this isn't an "official" proposal, It could definitely be expanded to any other intrinsic types (ARM NEON, x86 SSE2, x86 FMA, etc) to help explore any deficiencies or strong-points in the current exploration/etc. I feel like exploring how ARM and x86 intrinsics live SxS and what can or cannot be shared between the two will probably provide the most relevance.
OK I understand - will come up with some comments on it. I am personally interested in this feature as well :)
C#
namespace x86
{
public static unsafe class SSE
{
// This should be recognized by the JIT as a constant
// This indicates whether the hardware supports this instruction set.
public static bool IsSupported { get; }
I think that query for support of ISA extensions could be done either in RuntimeInformation class or by using new API from issue dotnet/runtime#22948 (let's call it here System.Diagnostics.Hardware) as this information could be useful not only for SIMD intrinsics but for Vector<T>
I think that query for support of ISA extensions could be done either...
I think, even if it was exposed through System.Diagnostics.Hardware, it should still be exposed directly on the respective intrinsic classes. That is, the information should be readily available where it is relevant (within the class that exposes the intrinsics tied to that flag).
My hope for this feature (and hopefully it matches the goals of CoreFX/CoreCLR) is that:
System.Math and System.MathF or System.String functions that is fast, efficient, vectorized, and written in managed code would be greatVector<T>) can be rewritten using these APIs.But how you can do System.Math using these APIs in a portable way? If you make call Math.Sqrt() the function SSE.SqrtScalar() how it could work on ARM?
The idea is to use a sort of "redirection" based on platform / architecture anyway?
@fanoI, in my mind, you would implement as follows:
C#
public static double Sqrt(double x)
{
if (x86.SSE2.IsSupported)
{
// Do x86 SSE2 implementation
}
else if (Arm.Neon.IsSupported)
{
// Do Arm implementation
}
else
{
// Do Software Fallback implementation
}
}
For a JIT, it would treat IsSupported as a constant (as it does with typeof(T) and Vector<T>.IsHardwareAccelerated) and would end up dropping the code-paths for irrelevant platforms and for ISAs that aren't supported.
For an AOT, it would likely drop irrelevant platforms and compile all ISAs. It would also likely emit code so that the method calls into the optimal ISA for the current CPU (via a jump table, dynamic dispatch, etc).
Intel hardware intrinsic API proposal has been opened at dotnet/corefx#22940
I can expect x86.SSE3.IsSupported, x86.SSE4.IsSupported and x86.AVX.IsSupported too?
Mmh those "if" / "else if" chains risk to become really long soon :-)
In Cosmos we can create plugs in X# directly so I'm unsure if this will be useful for us really or not... well we can plug x86.SSE2.Sqrt and hope that all "System.Math" does not need to be plugged being not native code anymore from our point of view.
Going to close this since we have an "official" proposal now.
Most helpful comment
The Intrinsic attribute should be internal implementation detail. It should not be part of the public surface.