Extend the vector types to have `char` as a supported element type
- Dominant language
- C#
- Stars
- 18.3k
- Forks
- 5.6k
- PR merge metrics
- PR metrics pending
Description
### Rationale
Code working with strings or string like data is frequently vectorized for performance. However, today this requires explicit checks and unsafe code to produce a `Vector128` from what is typically a `ReadOnlySpan` based input.
Given that `char` is part of the generic math feature (implements `IBinaryInteger`) and has always supported the full set of arithmetic operators in C# (i.e. `someChar /= someChar` works), it is proposed we just relax the restriction and allow `Vector128.IsSupported` to report `true`.
Doing this is mostly just relaxing an import restriction in the JIT.
### API Proposal
We can get away with as few as 1 API per type (`AsChar`). This allows all generic operations to work and only minimally excludes a handful of additional APIs which cannot be generic due to special considerations (such as changing type as part of their operation or for specific arguments). Users could then still access this functionality via `AsChar()` and `AsUInt16()`.
```csharp
public static partial class Vector128
{
public static Vector128 AsChar(this Vector128 vector);
}
// Repeat for Vector64, Vector256, Vector512, and Vector
```
However, doing so is a bit "unusual" and so for clarity the other APIs to fully match other `T` would be:
```csharp
public static partial class Vector128
{
// A user doing `Create('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h')` actually already binds to `Vector128` today
public static Vector128 Create(char e0, char e1, char e2, char e3, char e4, char e5, char e6, char e7);
// We have `M(...)` variants, the non-generic versions are older and so these are "consistency"
public static Vector128 Create(char value);
public static Vector128 Create(Vector64 lower, Vector64 upper);
public static Vector128 CreateScalar(char value);
public static Vector128 CreateScalarUnsafe(char value);
// Can be achieved with `AsUInt16()`
public static Vector128 Narrow(Vector128 lower, Vector128 upper);
public static Vector128 NarrowWithSaturation(Vector128 lower, Vector128 upper);
// Operators exist, these are just named variants
public static Vector128 ShiftLeft(Vector128 vector, int shiftCount);
public static Vector128 ShiftRightLogical(Vector128 vector, int shiftCount);
// Indices need to explicitly be integers (same for `float`/`double`)
public static Vectore128 Shuffle(Vector128 vector, Vector128 indices);
public static Vectore128 ShuffleNative(Vector128 vector, Vector128 indices);
// Can be achieved with `AsUInt16()`, notably no convention for `byte->char` exists so `WidenToChar` or something may also be warranted
public static (Vector128 Lower, Vector128 Upper) Widen(Vector128 vector);
public static Vector128 WidenLower(Vector128 vector);
public static Vector128 WidenUpper(Vector128 vector);
}
// Repeat for Vector64, Vector256, Vector512, and Vector
```
### Alternative Proposal
We could instead say that `Vector128` is unsupported and only expose APIs for loading/storing as `char` and have the vector it produces be `ushort`. We already have some such helpers internally. Much as before, we can also get away with fewer APIs if we want some cases to do something different.
```csharp
public static partial class Vector128
{
public static void CopyTo(this System.Runtime.Intrinsics.Vector128 vector, Span destination);
public static System.Runtime.Intrinsics.Vector128 Create(ReadOnlySpan values);
}
// Repeat for Vector64, Vector256, Vector512, and Vector
```
However, the set of APIs to match other `T` would be:
```csharp
public static partial class Vector128
{
// Can just use the Span overload
public static void CopyTo(this Vector128 vector, char[] destination);
public static void CopyTo(this Vector128 vector, char[] destination, int startIndex);
public static Vector128 Create(char[] values);
public static Vector128 Create(char[] values, int index);
// Casting the pointer is trivial, the API is `caller unsafe`
public static unsafe Vector128 Load(char* source);
public static unsafe Vector128 LoadAligned(char* source);
public static unsafe Vector128 LoadAlignedNonTemporal(char* source);
public static unsafe void Store(this Vector128 source, char* destination);
public static unsafe void StoreAligned(this Vector128 source, char* destination);
public static unsafe void StoreAlignedNonTemporal(this Vector128 source, char* destination);
// Can use `Unsafe.AsRef`, the API will be `caller unsafe`
public static unsafe Vector128 LoadUnsafe(ref readonly char source);
public static unsafe Vector128 LoadUnsafe(ref readonly char source, nuint elementOffset);
public static unsafe void StoreUnsafe(this Vector128 source, ref char destination);
public static unsafe void StoreUnsafe(this Vector128 source, ref char destination, nuint elementOffset);
}
// Repeat for Vector64, Vector256, Vector512, and Vector
```
Contributor guide
Assessment
This issue has not been assessed yet.