-
Notifications
You must be signed in to change notification settings - Fork 1.6k
rustc_scalable_vector
#3838
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
rustc_scalable_vector
#3838
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I do not think this is the correct approach nor implementation. It is invested in reusing an existing framework in repr(simd)
which has many undesirable features.
Thanks all for the feedback, I got a little bit busy after posting this, so I'll respond as soon as I can. |
Co-authored-by: Jamie Cunliffe <[email protected]>
5f5f7d2
to
9630780
Compare
Thanks everyone for the feedback, I've pushed a bunch of changes: Notable changes:
Small changes:
Hopefully these address many of the concerns raised so far. I don't expect it to address all of them, there are still big unresolved questions to be resolved. My intent with this is just to indicate the rough direction we plan to take so that I can justify an experimental implementation to try and find solutions to some of the issues raised. |
- Cannot be instantiated into generic functions (see | ||
[*Target features*][target-features]) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this also exclude e.g. size_of::<svfloat32_t>()
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It does, which is unfortunate and I'd prefer to support it. This restriction is only present as requiring the target feature be present when the type is used seems like it won't be possible, and I've not had time to experiment with using an indirect ABI (as per #3838 (comment)), so a restriction here makes the RFC, as written, technically feasible in the meantime.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How do C and C++ compilers deal with this dilemma? Do they support sizeof(svfloat32_t)
as runtime value, and if so, do they either require the target feature to be available for that or implicitly enable it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It looks like sizeless types are prohibited from being used with sizeof
:
<source>:9:5: error: invalid application of 'sizeof' to sizeless type 'svuint8x3_t' (aka '__clang_svuint8x3_t')
9 | sizeof(svuint8x3_t);
| ^ ~~~~~~~~~~~~~
1 error generated.
- Cannot have trait implementations (see [*Target features*][target-features]) | ||
|
||
- Including blanket implementations (i.e. `impl<T> Foo for T` is not a valid | ||
candidate for a scalable vector) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess this is intended to prevent a scalable vector value and types from bleeding into generic code, without relying entirely on post-mono errors? If so, note that they can also be smuggled in via associated types (slight modification of Ralf's earlier example):
trait Tr { type Assoc; }
fn foo<T: Tr>() {
size_of::<T::Assoc>();
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not just avoiding post-mono errors, but just avoiding any potential issues with target features needing to be be applied to trait methods but not the trait definition (#3820 is related to this). I'd expect that smuggling in instantiation via associated types to be prohibited by the existing restriction on generic instantiation - I don't think it's worth elaborating on that too much as I do intend to remove this restriction once investigating using an indirect ABI and hopefully finding that feasible.
- It may be possible to support scalable vector types without the target feature | ||
being enabled by using an indirect ABI similarly to fixed length vectors. | ||
|
||
- This would enable these restrictions to be lifted and for scalable vector | ||
types to be the same as fixed length vectors with respect to interactions | ||
with the `target_feature` attribute. | ||
|
||
- As with fixed length vectors, it would still be desirable for them to | ||
avoid needing to be passed indirectly between annotated functions, but | ||
this could be addressed in a follow-up. | ||
|
||
- Experimentation is required to determine if this is feasible. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
With an indirect ABI it should be possible to make use of scalable vector types without the sve
feature. You will need to avoid emitting any vscale
in the LLVM IR and use variable-length alloca/memcpy for any data movement involving such types. You will need to call a helper function to actually read VL to figure out how big the vectors are.
This could be done in rustc, but it's also possible that this support could be implemented directly in LLVM.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
imo the ABI for determining vscale
should be like:
static VSCALE: AtomicUsize = AtomicUsize::new(0);
#[cold]
fn vscale_slow() -> usize {
let mut vscale = 1; // default if no features are available
#[cfg(any(target_arch = "aarch64", target_arch = "arm64ec"))]
if is_aarch64_feature_detected!("sve") {
vscale = svcntb() as usize / 16;
}
// add other arches here
VSCALE.store(vscale, Ordering::Relaxed);
NonZeroUsize::new(vscale).unwrap()
}
#[inline(always)]
pub fn vscale() -> NonZeroUsize {
NonZeroUsize::new(VSCALE.load(Ordering::Relaxed)).unwrap_or_else(vscale_slow)
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually I think you can get away with just a function containing svcntb
if you assume that the presence of an SVE type implies that the sve
feature is enabled since that is required to create an instance of such a type in the first place.
Unfortunately this doesn't work for MaybeUninit<svint32_t>
since you then have to know the size without the guarantee that an sve
-feature function has previously been executed. Is MaybeUninit
expected to support scalable vector types?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Then again, this doesn't need to be super optimized since this is for the slow path: we will most likely lint against any use of a scalable vector type in a function without the appropriate features, and consider it a user mistake to do such a thing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually I think you can get away with just a function containing
svcntb
if you assume that the presence of an SVE type implies that thesve
feature is enabled since that is required to create an instance of such a type in the first place.
no you can't -- you can make a sve type even without the sve feature with something like (avoiding zeroed
, read_unaligned
, etc. since they would require putting sve types in generics):
fn zeroed() -> svuint8_t {
#[repr(C, align(16)] // I'm assuming 16 is enough
struct Zeros([u8; 2048]);
const ZEROS: &'static Zeros = &Zeros([0; 2048]); // long enough for max vl
unsafe { *(ZEROS as *const _ as *const svuint8_t) }
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If it was that simple, LLVM could also just support vscale
changing at runtime. Many people (including me) have been bashing their head against this wall over the years. Two major problems, in brief:
- Pretty much no code anywhere, both in compilers and in the code being compiled, is prepared to deal with the meaning of a type shifting during program execution. It's not just that
size_of::<T>()
changes and can't be cached. Nothing that in any way depends on the size can flow across the "fence". That notably also includes every value of the types that got reinterpreted. Even apparently trivial code likelet x = make_scalable_vector(); change_vsize(); let y = x;
is broken. I don't even know how to write a precise language spec for that. - Nothing in the world of optimizing compilers is really prepared to treat basic operations like "allocate space for a local variable" or loads/stores/arithmetic to have an implicit dependency on some global state that must not be reordered across some sort of fence (especially if the "vscale change" can be hidden inside a function call, rather than being embedded into the static structure of the program representation).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not proposing LLVM's idea of vscale
changes, just that Rust's fallback value changes between the SVE-disabled value (probably 1
, it could also be None
so size_of
or any other size-dependent operations panics and/or aborts) and LLVM's idea of what vscale
is. The intrinsic would be unsafe
with a safety condition that there aren't any scalable vectors (on this thread? needs a thread-local vscale
cache)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This doesn't really solve any of the problems because they also apply to surface Rust, to any formalization of Rust's semantics, and to rustc's MIR.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
With an indirect ABI it should be possible to make use of scalable vector types without the sve feature. You will need to avoid emitting any vscale in the LLVM IR and use variable-length alloca/memcpy for any data movement involving such types. You will need to call a helper function to actually read VL to figure out how big the vectors are.
I expect this will be the only way to make these types feasible, as requiring the target feature be present when the type is used is looking like it isn't. I intend to experiment with this or something like it and see what problems we run into.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Even if the restrictions in the current RFC are extended to plug this hole, I believe the end goal is closer to what’s described in the Sized Hierarchy RFC: scalable vectors should be
Copy
andSized
, just notconst Sized
. So things likesize_of::<svfloat32_t>()
andMaybeUninit<svfloat32_t>
ought to work, and don’t require any values of scalable vector type to have ever existed.
I think this would be ideal but I'll settle for whatever is feasible, if that means that these types exist and you can only really use them with the vendor intrinsics, then that's fine. I think using an indirect ABI will get us at least that far, but I expect we'd need something more to support generic types and functions that have no notion of the relevant target features.
9630780
to
442beea
Compare
Supercedes #3268.
Introduces a new attribute,
#[rustc_scalable_vector(N)]
, which can be used to define new scalable vector types, such as those in Arm's Scalable Vector Extension (SVE), or RISC-V's Vector Extension (RVV).rustc_scalable_vector(N)
is internal compiler infrastructure that will be used only in the standard library to introduce scalable vector types which can then be stabilised. Only the infrastructure to define these types are introduced in this RFC, not the types or intrinsics that use it.This RFC has a dependency on #3729 as scalable vectors are necessarily non-const
Sized
.There are some large unresolved questions in this RFC, the current purpose of the RFC is to indicate a intended direction for this work to justify an experimental implementation to help resolve those unresolved questions.
Rendered