Skip to content

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

Open
wants to merge 25 commits into
base: master
Choose a base branch
from
Open

Conversation

davidtwco
Copy link
Member

@davidtwco davidtwco commented Jul 14, 2025

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

Copy link
Member

@workingjubilee workingjubilee left a 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.

@davidtwco
Copy link
Member Author

Thanks all for the feedback, I got a little bit busy after posting this, so I'll respond as soon as I can.

@davidtwco davidtwco changed the title repr(scalable) rustc_scalable_vector Aug 7, 2025
@davidtwco
Copy link
Member Author

davidtwco commented Aug 7, 2025

Thanks everyone for the feedback, I've pushed a bunch of changes:

Notable changes:

  • f514a3e renames this from repr(scalable) to rustc_scalable_vector
    • I wasn't aware of objections to repr(simd) and I don't have an especially strong preference as to what we call this, the framing that it was an extension of repr(simd) was largely syntactic and quite shallow, so I've changed it to rustc_scalable_vector
  • 83d0b93 clarifies which types are accepted with the type marker
  • dcb29b4 avoids partially introducing scalable vectors in the guide-level explanation
  • 388170a is a significant change to how we explain scalable vectors and justify the manual specification of N
    • As I said in rustc_scalable_vector #3838 (comment), I'm still open to changing this, but I don't think it's trivial and think manually specifying N is likely to be okay, happy to hear if my justification of that point isn't convincing
    • Crucially, it adds ASCII diagrams, and every RFC is better with ASCII diagrams
  • 16bb4eb adds a bunch of restrictions to these types to avoid issues with target_feature
    • I don't like these restrictions and I want to find a way to remove them, so I've made this change so that the RFC isn't just entirely infeasible, but I've got some ideas that I'd like to explore for how to fix the issues here
    • 9466d26 adds another possibility for how we might be able to avoid these restrictions
  • ca4aa06 mentions that these types ought to be considered FFI-safe
  • 5011dc6 adds lots of prior art, reviewing the issues related to repr(simd) and target_feature and their relevance to scalable vectors
  • 6186245 relaxes some of the restrictions about these types' use in compound types following changes in LLVM and describes how tuples of vectors will be defined
  • c6d836d clarifies how the ABI requirements of these types will impact function pointers
  • 530378c clarifies how the ABI requirements of these types will impact dyn compatibility

Small changes:

  • 2a7705a adds asserts into the example of using scalable vectors
  • 8709d8c adds some missing words
  • b96e2b7 clarifies some of the ambiguous parts of the code example and what the intrinsics are doing
  • 4123a3a fixes a typo
  • e5b546c removes trailing whitespaces
  • 494da8a moves the warning about prctl into a subsection
  • 968ae3e removes an incorrect detail from our description of the "no action taken" alternative
  • 9630780 removes a reference to repr(simd) that is no longer relevant

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.

Comment on lines +274 to +273
- Cannot be instantiated into generic functions (see
[*Target features*][target-features])

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>()?

Copy link
Member Author

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.

Copy link

@hanna-kruppe hanna-kruppe Aug 12, 2025

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?

Copy link
Member Author

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.

Comment on lines +277 to +278
- 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)

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>();
}

Copy link
Member Author

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.

Comment on lines +1158 to +1167
- 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.
Copy link
Member

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.

Copy link
Member

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)
}

Copy link
Member

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?

Copy link
Member

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.

Copy link
Member

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.

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) }
}

Copy link

@hanna-kruppe hanna-kruppe Aug 8, 2025

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:

  1. 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 like let 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.
  2. 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).

Copy link
Member

@programmerjake programmerjake Aug 8, 2025

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)

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.

Copy link
Member Author

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.

Copy link
Member Author

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 and Sized, just not const Sized. So things like size_of::<svfloat32_t>() and MaybeUninit<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.

@Ruanyx1823

This comment was marked as off-topic.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

8 participants