Repository navigation
[API Proposal]: Example usages of a VectorSVE API #88140
Copy link
Copy link
Closed
Labels
api-suggestionEarly API idea and discussion, it is NOT ready for implementationEarly API idea and discussion, it is NOT ready for implementationarch-arm64area-System.Runtime.Intrinsics
Milestone
Description
Background and motivation
Adding a vector API for Arm SVE/SVE2 would be useful. SVE is a mandatory feature in Arm 9.0 onwards and is an alternative to NEON. Code written in SVE is vector length agnostic and will automatically scale to the vector length of the machine it is running on, and therefore will only require a single implementation per routine. Use of predication in SVE enables loop heads and tails to be skipped, making code shorter, simpler and easier to write.
This issue provides examples of how such an API might be used.
API Proposal
None provided.
API Usage
/*
Sum all the values in an int array.
*/
public static unsafe int sum_sve(ref int* srcBytes, int length)
{
VectorSVE<int> total = Sve.Create((int)0);
int* src = srcBytes;
VectorSVEPred pred = Sve.WhileLessThan(i, length);
/*
WhileLessThan comes in two variants:
VectorSVEPred WhileLessThan(int val, int limit)
VectorSVEComparison WhileLessThan(VectorSVEPred out predicate, int val, int limit)
A VectorSVEComparison can be tested using the SVE condition codes (none, any, last, nlast etc).
`if (cmp.nlast) ....`
`if (Sve.WhileLessThan(out pred, i, length).first) ....`
`if (cmp)` is the same as doing `if (cmp.any)`
Ideally the following will not be allowed:
auto f = Sve.WhileLessThan(out pred, i, length).first
*/
/*
Always using a function call for the vector length instead of assigning to a variable will allow
easier optimisation to INCW (which is faster than incrementing by a variable).
*/
for (int i = 0; Sve.WhileLessThan(out pred, i, length); i += Sve.VectorLength<int>())
{
VectorSVE<int> vec = Sve.LoadUnsafe(pred, ref *src, i);
/*
This is the standard sve `add` instruction which uses a merge predicate.
For each lane in the predicate, add the two vectors. For all other lanes use the first vector.
*/
total = Sve.MergeAdd(pred, total, vec);
}
// No tail call required.
return Sve.AddAcross(total).ToScalar();
}
/*
Sum all the values in an int array, without predication.
For performance reasons, it may be better to use an unpredicated loop, followed by a tail.
Ideally, the user would write the predicated version and the Jit would optimise to this if required.
*/
public static unsafe int sum_sve_unpredicated_loop(ref int* srcBytes, int length)
{
VectorSVE<int> total = Sve.Create((int)0);
int* src = srcBytes;
int i = 0;
for (i = 0; i+Sve.VectorLength<int>() <= length; i+= Sve.VectorLength<int>() )
{
VectorSVE<int> vec = Sve.LoadUnsafe(ref *src, i);
total = Sve.Add(total, vec);
}
// Predicated tail.
VectorSVEPred pred = Sve.WhileLessThan(i, length);
VectorSVE<int> vec = Sve.LoadUnsafe(pred, ref *src, i);
total = Sve.MergeAdd(pred, vec, total);
return Sve.AddAcross(total).ToScalar();
}
/*
Count all the non zero elements in an int array.
*/
public static unsafe int CountNonZero_sve(ref int* srcBytes, int length)
{
VectorSVE<int> total = Sve.Create((int)0);
int* src = srcBytes;
VectorSVEPred pred = Sve.WhileLessThan(i, length);
VectorSVEPred true_pred = Sve.CreatePred(true);
for (int i = 0; Sve.WhileLessThan(out pred, i, length); i += Sve.VectorLength<int>())
{
VectorSVE<int> vec = Sve.LoadUnsafe(pred, ref *src, i);
VectorSVEPred cmp_res = Sve.CompareGreaterThan(pred, vec, 0);
total = Sve.MergeAdd(cmp_res, total, vec);
}
// No tail call required.
return Sve.AddAcross(total).ToScalar();
}
/*
Count all the non zero elements in an int array, without predication.
*/
public static unsafe int CountNonZero_sve_unpredicated_loop(ref int* srcBytes, int length)
{
VectorSVE<int> total = Sve.Create((int)0);
int* src = srcBytes;
VectorSVEPred pred = Sve.WhileLessThan(i, length);
VectorSVEPred true_pred = Sve.CreatePred(true);
// Comparisons require predicates. Therefore for a truely non predicated version, use Neon.
int vector_length = 16/sizeof(int);
for (int i = 0; i+vector_length <= length; i+=vector_length)
{
Vector128<int> vec = AdvSimd.LoadVector128(src);
Vector128<int> gt = AdvSimd.CompareGreaterThan(vec, zero);
Vector128<int> bits = AdvSimd.And(gt, one);
total = AdvSimd.Add(bits, total);
src += vector_length;
}
// Predicated tail.
VectorSVEPred pred = Sve.WhileLessThan(i, length);
VectorSVE<int> vec = Sve.LoadUnsafe(pred, ref *src);
VectorSVEPred cmp_res = Sve.CompareGreaterThan(pred, vec, 0);
total = Sve.MergeAdd(cmp_res, total, vec);
return Sve.AddAcross(total).ToScalar();
}
/*
Count all the elements in a null terminated array of unknown size.
*/
public static unsafe int CountLength_sve(ref int* srcBytes)
{
int* src = srcBytes;
VectorSVEPred pred = Sve.CreatePred(true);
int ret = 0;
while (true)
{
VectorSVE<int> vec = Sve.LoadUnsafeUntilFault(pred, ref *src); // LD1FF
/*
Reading the fault predicate via RDFFRS will also set the condition flags:
VectorSVEComparison GetFaultPredicate(VectorSVEPred out fault, VectorSVEPred pred)
*/
VectorSVEPred fault_pred;
if (Sve.GetFaultPredicate(out fault_pred, pred).last)
{
// Last element is set in fault_pred, therefore the load did not fault.
/*
Like WhileLessThan, comparisons come in two variants:
VectorSVEPred CompareEquals(VectorSVEPred pred, VectorSVE a, VectorSVE b)
VectorSVEComparison CompareEquals(VectorSVEPred out cmp_result, VectorSVEPred pred, VectorSVE a, VectorSVE b)
*/
// Look for any zeros across the entire vector.
VectorSVEPred cmp_zero;
if (Sve.CompareEquals(out cmp_zero, pred, vec, 0).none)
{
// No zeroes found. Continue loop.
ret += Sve.VectorLength<int>();
}
else
{
// Zero found. Count up to it and return.
VectorSVEPred matches = Sve.PredFillUpToFirstMatch(pred, cmp_zero); // BRKB
ret += Sve.PredCountTrue(matches); // INCP
return ret;
}
}
else
{
// Load caused a fault.
// Look for any zeros across the vector up until the fault.
VectorSVEPred cmp_zero;
if (Sve.CompareEquals(out cmp_zero, fault_pred, vec, 0).none)
{
// No zeroes found. Clear faulting predicate and continue loop.
ret += Sve.PredCountTrue(fault_pred); // INCP
Sve.ClearFaultPredicate(); // SETFFR
}
else
{
// Zero found. Count up to it and return.
VectorSVEPred matches = Sve.PredFillUpToFirstMatch(pred, cmp_zero); // BRKB
ret += Sve.PredCountTrue(matches); // INCP
return ret;
}
}
}
}
Alternative Designs
No response
Risks
References
SVE Programming Examples
A64 -- SVE Instructions (alphabetic order)
No response
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
api-suggestionEarly API idea and discussion, it is NOT ready for implementationEarly API idea and discussion, it is NOT ready for implementationarch-arm64area-System.Runtime.Intrinsics
Activity
a74nh commentedon Jun 28, 2023
@kunalspathak @tannergooding @BruceForstall
a74nh commentedon Jun 28, 2023
@TamarChristinaArm
kunalspathak commentedon Jun 28, 2023
cc: @JulieLeeMSFT
tannergooding commentedon Jun 29, 2023
Thanks for opening the issue! This is definitely a space we want to improve, just wanting to lay out some baseline information and expectations for this.
It's worth noting that this is not actionable as an API proposal in its current setup. Any API proposal needs to follow the API proposal template including defining the full public surface area to be added.
It's also worth noting any work towards SVE/SVE2 is going to require:
For 1, there are only some mobile chips (Samsung/Qualcomm) and the AWS Graviton3 that currently support SVE or Armv9 at all. The latter is the only one that would be viable for CI and it would likely require some non-trivial effort to make happen. It may still be some time and require higher market saturation of SVE/SVE2 before work/progress can be made (the same was true for AVX-512). Having more easily accessible hardware that supports the ISAs (both for CI and local development) can accelerate this considerably.
For 2, SVE is a very different programming model from the existing fixed sized vectors exposed by
System.Runtime.Intrinsics. It is closer to, but not entirely aligned with,System.Numerics.Vector<T>instead. We had a similar situation with the masking support for AVX-512 and we opted to not expose it for .NET 8. Instead we opted to do pattern recognition and implicit lightup for the existing patterns users already have to use downlevel.It is entirely possible that SVE will be in a similar boat and in order for the BCL and general user-base to best take advantage of it, we will need to consider a balance between the general programming model most user-code needs for other platforms and what exists in hardware directly.
That is, the simplest way to get SVE support available, without requiring all users to go rewrite their code and add completely separate code paths just for the subset of hardware with SVE support would be to have
SVElight-up overVector<T>. Many of the same predicate/masking patterns will already be getting recognized for AVX-512 and simply involve tracking when a given node returns amask(such as comparisons) or takes amask(such asConditionalSelect)For 3, native SVE has many limitations around where the SVE vector types can be declared and how they are allowed to be used. Getting the same restrictions in managed would be very complex and not necessarily "pay for play". For the JIT, this is fine since things can be determined at runtime and match exactly. For AOT, this represents a problem since the sizes and other information isn't known until runtime which can complicate a number of scenarios.
TamarChristinaArm commentedon Jun 30, 2023
@tannergooding Thanks for the initial feedback, just some quick responses:
There are actually some more that are available. If Linux would be an acceptable target I can send you an email with other options than the ones you have stated here.
Agreed 100%, though the purpose of this ticket is to start the discussions on the SVE intrinsics that would form the basis of
Vector<T>. Our expectations is thatVector<T>will provide "good enough" support, but for people who want to use the full capabilities of SVE they would require direct usage of the intrinsics. This pull request was more to highlight some of the difficulties in archiving this and start the discussions around this. In some of the examples for instance we require explicit access to theCCflags which affects which branch gets emitted.Before doing a full SVE design and proposal, it's better for us to get some input from you folks on how you'd like these kinds of situations to be handled. From our last discussion we highlighted that SVE would need some additional JIT design work for workloads that Vector won't ever cover, such as SME. Therefore I think it's best to focus this issue on how we can even provide core Scalable vector support for current and future Arm architectures rather than how to support them in the generic wrappers. I believe one is the pre-requisite to the other?
Agreed! one of the questions we had raised last time was what are the constraints on which AOT operates. If AOT is equivalent to a native compiler's
-mcpu=nativethan this won't be an issue as you'd know the vector length you're targetting.If AOT is supposed to be platform agnostic, than the solution could be the one we discussed 2 years ago, where instead of assuming VLA, we assume a minimum vector length that the user selects. i.e. if they want full compatibility we select VL128 and just simply predicate all SVE operations to this.
This fixes the AOT issues since we'd never use more than VL sized vectors.
But really, to move forward with SVE support we need your input. SVE is much more than just a vector length play, and without support for it .NET the ability to improve will be quite limited. SVE will, and does have capabilities that Advanced SIMD will never have.
tannergooding commentedon Jun 30, 2023
Linux is great!. We just really need to ensure there are options we can use for both development and validation. This namely means they can't be exclusively mobile devices (i.e. phones). Having a list of viable devices would be good, all the sources I've found for SVE or Armv9 have shown to be phones, android devices, the latest version of Gravitron, or not yet released for general use.
It depends on the general approach taken. We could very likely expose some
SVE/SVE2support viaVector<T>or evenVector64/128/256/512<T>without exposing the raw intrinsic APIs.This model is actually how AVX-512 was designed to be exposed for .NET 8. That is, we planned to ship the general purpose
Vector512<T>and then implicit light-up for existing patterns overVector128/256<T>. The support forVectorMask<T>and the platform specific APIs were an additional stretch goal. We ended up having enough time to do the platform specific APIs and to start work onVectorMask<T>, the latter of which we ultimately determined was not going to be a good fit and was cut for the time being.We went with this approach because it allowed easier access to the acceleration for a broader audience, worked well when considering all platforms (not just x86/x64), allowed existing code to improve without explicit user action, and allowed developers to iteratively extend their existing algorithms without having to add massive amounts of new platform specific code.
The default for most AOT would be platform agnostic. For example,
x64AOT still targets the 2004 baseline and has opportunistic light-up (that is allows for a cached runtime based check to access SSE3-SSE4.2).Arm64AOT still targets the v8.0 baseline and likewise has opportunistic light-up for some scenarios.Predicated instructions has an increased cost over non-predicated, correct?
In particular, we have at least 3 main platforms to consider support around:
There are also other platforms we support (Arm32, x86), platforms that are being brought online by the community (LoongArch, RISC-V), etc.
Most of these platforms have some amount of SIMD support and giving access to the platform specific APIs gives the greatest amount of power in your algorithms. Exposing this support long term is generally desirable.
However, an algorithm supporting every one of these platforms is also very expensive and most of the time the algorithms are fairly similar to each other regardless of the platform being targeted and there's often only minor differences between them. This is why the cross platform APIs were introduced in .NET 7, as it allowed us to take what was previously a minimum of 3 (Arm64, Fallback, x64) but sometimes more (different vector sizes, per ISA lightup, other platforms, etc) paths and merge them down to just 2 paths (Vector, Fallback) while still maintaining the perf or only losing perf within an acceptable margin.
To that end, the general direction we'd like to maintain is that developers can continue writing (for the most part) more general cross platform code for the bulk of their algorithms. They would then utilize the platform specific intrinsics for opportunistic light-up within these cross platform algorithms where it makes a significant impact. Extreme power users could continue writing individual algorithms that are fine-tuned for each platform/architecture, but it would not necessarily be the "core" scenario we expect most users to be targeting (namely due to the complexity around doing this).
I believe that ensuring
SVE/SVE2can fit into this model will help increase its adoption and usage, particularly among developers who may not have access to the full breadth of "latest hardware" on which to test their code for every platform/architecture. It will also help ensure that users don't necessarily need to go and rewrite their code to benefit, they can simply roll forward to the latest version of .NET and implicitly see performance wins. The BCL and other high performance libraries (including domain specific scenarios, such as ML.NET, Image Processing, etc), would update their own algorithms to make targeted usage of the platform specific APIs where they help achieve significantly more performance than the general purpose code.I do think we should be able to make
SVE/SVE2integrate with the same general model and I believe we should be able to do this aroundVector<T>rather than introducing a newArm64exclusive vector type.Given the samples in the OP, there's this callout:
I actually think its the inverse and given the cross platform considerations, we want users to write code unpredicated with a tail loop. This is what they already have to do for
NEON, it's what they have to do forWASM, it's what they have to do forSSE/AVX. This is also something we considered aroundAVX-512masking support and one reason why we opted to not exposeVectorMask<T>for right now.Instead, we could express the code such as:
We would still of course expose
Sve.Add,Sve.AddAccross,Sve.LoadVector,Sve.WhileLessThan, etc. However, users wouldn't have to use it, it would just be an additional option if they wanted finer grained control over what codegen they'd get.There are notably a few other ways this could be done as well, but the general point is that this results in very natural and easy to write code that works on any of the platforms, including on hardware that only supports
NEON, without users having to write additional and separate versions of their algorithms to achieve it.There is a note that
CreateTrailingMask,GreaterThan, etc all return aVector<T>and not aVectorMask<T>(orVectorSVEPred). This is something that we're doing with AVX-512 due to the consideration thatVectorMask<T>could not be easily accelerated on downlevel platforms and that it would effectively need to be aVector<T>in those cases anyways. Such a restriction meant users would have to explicitly rewrite their algorithms to use masking, and that was going to hinder adoption overall.The JIT handles this difference from hardware in lowering by doing some tracking to know if the value is actually a
maskorvectorand whether the consumer needs amaskorvector. This then translated very cleanly to existing patterns such asConditionalSelect(mask, x + y, mergeVector)which can be trivially recognized and emitted as aMergeAddinstead. The same support translates to all other intrinsic APIs that support masking, so we didn't need to expose an additional 3800 API overloads to support masking (this was the minimum actual count of additional API's we'd of had to expose to exactly match what hardware supports).Noting that this decision also doesn't restrict our ability from adding such support in the future. It just greatly simplified what was required today and made it much faster and more feasible to get the general support out while also keeping the implementation cost low.
We could also add support for the fully predicated loops and optimizing them to be rewritten as unpredicated with a post predicated handler. But that is more complicated and I believe would be better handled as a future consideration after we get the core support out.
I'd like to see us add SVE support and so I'd hope going along the avenues we've found to work and help with adoption and implicit light up would also apply here.
a74nh commentedon Jul 3, 2023
Agreed, this seemed the closest category. Given that this won't turn into a full API proposal, I could just drop the category and move to a normal issue?
For now, yes.
Having AOT Arm64 target 8.0 feels sensible. For ReadyToRun, it would then tier appropriately to what is available. For the "no JIT, only AOT" option, maybe a command line option at compile time - essentially the same as the gcc/llvm
-marchflags.Without explicit VectorMask types you're losing type checking. Could VectorMask just be a wrapper over Vector?
On downlevel platforms, the tail is being turned into a scalar loop? Therefore on simple loops, the mask is effectively optimised away, right?
That makes sense when coming at it from the generic side.
From an implementation side, I'm thinking that predication makes things easier. The user writes a single predicated loop. There is enough information in the loop for the jit to 1) create a predicated loop with no tail or 2) create unpredicated main loop and predicated tail or 3) create unpredicated main loop and scalar tail.
I understand users are not used to writing with predication yet. But, in the example, the user still has to use predication for the tail. Is that why
VectorMask<T>was dropped for now - to make it easier for users?Another option maybe would be to never have
Vector<T>supporting masks/predication. Tails would always be scalar. All platforms can be supported easily.Then later
Vector<P>is later added which looks similar, but does everything using predication - and the jit turns into the correct thing on the platform.That would skip ever having unpredicated loops with predicated tail, but maybe that's ok.
tannergooding commentedon Jul 3, 2023
We have this already, and allow targeting specific versions (
armv8.1,armv8.2, etc) or specific ISAs that are currently supported.Not cheaply/easily and not without requiring users go and explicitly opt into the use of masking.
The general point is that on downlevel hardware (
NEON,SSE-AVX2, etc) there is no masking, you only haveVector<T>. If you want to do masking, you have to use explicit APIs likeConditionalSelectorMaskMoveand so this is what developers are already doing.It is fairly trivial for the JIT to recognize and internally do the correct type tracking. It is also fairly trivial for the JIT to insert the relevant implicit conversions if an API expects a mask and was given a vector; or if it expects a vector and was given a mask. One of the reasons this is cheap and easy to do is because it is completely non-observable to whoever is writing the algorithm and so the JIT can do this in a very "pay for play" manner.
If we inverse things, such that we expose
VectorMaskpublicly then we start having to worry about observable differences in various edge cases. We start having to expose overloads that explicitly take the mask, which means most new APIs now have2xthe overloads. We also have to do significantly more work to ensure that the right conversions and optimizations are being done such that it remains efficient, etc.Users do different things for different scenarios. Some users simply defer back to the scalar path. Some will "backtrack" and do 1 more iteration when the operation is idempotent. Some use explicit masked operations to ensure the operation remains idempotent.
I'd disagree on this. The general "how to write vectorized code" steps are:
noperations per iteration (wherenis typicallysizeof(Vector) / sizeof(T), that isVectorXXX<T>.Count)noperations into 1 SIMD operation per iteration (e.g.n scalar Addbecome1x SIMD Add)Predication/masking is effectively a more advanced scenario that can be used to handling more complex scenarios such as branching or handling the tail (remaining) elements that don't fill the vector. It's not something that's actually needed super often and generally represents a minority of the overall SIMD code you write.
Right. We opted to drop
VectorMask<T>(from the AVX-512 support) for now because it was significantly less work for the JIT and also greatly simplified the end user experience. It made it so that users don't have to change how they are thinking about or writing their code at all, they simply continue doing what they've already been doing and it happens to take advantage of the underlying hardware if the functionality is available.It also means that we don't have to do more complex handling for downlevel hardware (which is currently the far more common scenario) and can instead restrict it only to the newer hardware. It means that we won't typically encounter unnecessary predication which means we don't have to worry about optimizing the unnecessary bits away. It then also simplifies the general handling of loops as we don't have to consider predication as part of loop cloning, loop hoisting, or other optimizations in general.
Users are already sometimes writing predicated tails today, so I don't see the need to block this. I think it would be better to just expose the couple additional APIs that would help with writing predicated tails instead. Exposing a nearly identical type that supports predication would just make adding predication support harder and would increase the amount of code the typical user needs to maintain.
In my opinion, this is largely a non-issue. Downlevel users already need to do their masking/predication using
VectorXXX<T>and so they're already used toVector128.GreaterThanreturning aVector128<T>and not aVectorMask128<T>.The JIT is then fully capable of knowing which methods return a "mask like value", which we already minimally track on downlevel hardware to allow some special optimizations.
The JIT is also fully capable of correctly tracking the type internally as
TYP_MASK(rather thanTYP_SIMD16) when it is not just "mask like" but an actual mask (for AVX-512 or SVE capable hardware). This makes the fact that it is actually a mask completely invisible to the end user.The only requirement for the JIT here is that if it has a
TYP_MASKand the API expects aTYP_SIMD16(or vice-versa) it needs to insert an implicit conversion. On AVX-512, there are dedicated instructions for this. I don't believe there are for SVE, but there are still some options available that can make this very cheap/feasible.A managed analyzer can then correctly surface to the end user when they are inefficiently handling masking (e.g. passing in a mask to something that doesn't expect one, or vice versa). This is very similar to the
[ConstantExpected]analyzer and would likely not trigger for typical code users are writing.12 remaining items
a74nh commentedon Oct 20, 2023
After parsing through the
FEAT_SVE, I've split into groups, trying to keep within 200 methods per group.That's 2307 C# methods across 27 groups.
Here's a truncated
apiraw_FEAT_SVE__math.csAdding
FEAT_SVE2and the other SVE extensions gets us to 4061 methods in 50 groups. Although I still need to do some further parsing for the other extensions.There's lots of parsing to fix up namings and types. Plus predicates have been stripped out where necessary (eg
pgin the methods above).If there's nothing obvious that need fixing in the above, then I can start posting a few of them as API requests next week.
tannergooding commentedon Oct 20, 2023
At a glance those look correct.
Noting that for the purposes of API review, having bigger issues is fine and a lot of the information can be compressed down.
Consider for example how we did AVX512F here: #73604. We have all the necessary APIs, but we don't provide information unnecessary to API review such as what instruction or native API it maps to.
Given how large
SVEandSVE2are, I wouldn't consider it unreasonable to compress the data further. For example, you could imagine it represented as:We aren't going to reject an API for a particular
There and so we really care mostly about the general signature, name, and exactly what T will be supported.For
public static Vector<T> SubtractSaturate(Vector<T> left, T right);, it looks like this is effectively a special encoding ofSubtractSaturate(left, Broadcast(right));whenrightis a constant in range (typically0-255or a multiply of256), is that correct?This is going to be very hard to represent to users "correctly" as it requires
rightto be a constant. We can support contiguous ranges using[ConstantExpected]but we don't have a way to represent something like "multiple of 256 between [256, 65280]". So I wonder if it would be better to simply expose those asSubtractSaturate(left, Broadcast(right));and simply optimize it down when the input is valid. Thoughts?a74nh commentedon Oct 23, 2023
It's actually more complicated to reduce it down to
Tversions. I'm starting with the C ACLE which is already split into types.For quite a few methods there are two types (usually one is twice the size of the other). Eg:
And then there are others that take in scalars, an it's not immediately obvious should the scalar be a
Tor is it always fixed. Eg: for aload(), the index should always belong, but in a math operation alongis probably matched toT.However, given everything below, this might become obvious.
Ok, that makes sense.
The way this is done in the C ACLE is
When op2 is in range[1], this intrinsic can use:
SQSUB Ztied1.S, Ztied1.S, #op2 // [2]
When -op2 is in range[1], this intrinsic can use:
SQADD Ztied1.S, Ztied1.S, #-op2 // [2]
The general implementation is:
SQSUB Zresult.S, Zop1.S, Zop2.S
[1] This is true if the value is in the range [0, 255] or is a multiple of 256 in the range [0, 65280]
[2] If instead result is in a different register from the inputs, the compiler can add a preceding MOVPRFX (unpredicated) instruction
.... which is effectively the same thing, but just different choices on when/how the optimal version is selected.
Given the C# API is higher level than just mapping directly down to the architecture, then it makes sense to implicitly optimise where possible, using just the vector only version.
A quick grep suggests that might reduce the API by another 1686 methods.
a74nh commentedon Apr 15, 2025
SVE has been implemented, SVE2 is still awaiting review. Those are all in separate issues.
This issue doesn't appear to be an over arching story for SVE and isn't directly tracking anything.
Therefore I'm closing this issue.
@JulieLeeMSFT please reopen if I'm wrong.