Introduce safe SIMD API, port some simple vectorized operations to use it #604
+2,546
−105
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
This PR is the first step in addressing #549, which will significantly reduce the amount of unsafe code overall in rten.
It introduces a new API for the rten-simd portable SIMD library in
rten_simd::safe
, which allows defining SIMD operations using safe code, and ports some of the simpler vectorized operations in rten-vecmath (Sum, SumSquare, SumSquareSub, MinMax, Normalize) to use it.The basic idea behind the API design is to separate SIMD data types (which can always be constructed) and operations (which require hardware support), and enforce that the types implementing the operations can only be constructed if the ISA is supported. This is similar to pulp, which appears to be a Rust-ified version of Highway. One important difference is that the SIMD traits are designed in a way which makes it easier to reuse code for vectorized operations on different data types. To reduce the need for unsafe operations to load and store SIMD vectors, iterators and functional utilities (map, fold etc.) on slices are provided, which handle the load/store internally.
Design notes
Unified versus separate types for data and operations
An earlier version of this PR used a different design where operations were implemented as methods of the SIMD data types, similar to
std::simd
. This has several advantages compared to what is implemented here:std::ops
traits, so you can use expressions like (a * b + c
)ops.mul(x, ops.splat(2))
versusops.splat(2).mul(x)
orops.splat(2) * x
).With such a design any operation that constructs a SIMD vector needs to somehow enforce that the instruction set is supported. This requires wrapping the built-in SIMD types (
__m256
etc.) with a newtype and then enforcing that constructing instances of the newtype requires some kind of proof that the ISA is supported. I did this earlier by moving all constructing operations to a separate trait. This made the overall set of traits more complex however.Support for scalable vectors
One of the reasons that Highway separates operations and data is because they want to support scalable vectors with a size that is unknown at compile time, and such compiler builtin types cannot be wrapped in classes in C++ in order to implement the operations as methods. In Rust it isn't completely settled yet what restrictions SVE types will have, but it seems that the newtype pattern is expected to work. If so, then both designs would work with all vector types.
TODO:
main
on Armmain
on x64