diff --git a/text/3806-align-attr.md b/text/3806-align-attr.md new file mode 100644 index 00000000000..baafe4a66ae --- /dev/null +++ b/text/3806-align-attr.md @@ -0,0 +1,542 @@ +- Feature Name: `align_attr` +- Start Date: 2025-05-01 +- RFC PR: [rust-lang/rfcs#3806](https://github.com/rust-lang/rfcs/pull/3806) +- Rust Issue: [rust-lang/rust#0000](https://github.com/rust-lang/rust/issues/0000) + +# Summary +[summary]: #summary + +Add an `#[align(…)]` attribute to set the minimum alignment of `struct` and +`enum` fields, `static`s, functions, and local variables. + +# Motivation +[motivation]: #motivation + +## Bindings to C and C++ + +[C](https://en.cppreference.com/w/c/language/_Alignas) and +[C++](https://en.cppreference.com/w/cpp/language/alignas) provide an `alignas` +modifier to set the alignment of specific struct fields. To represent such +structures in Rust, `bindgen` is sometimes forced to add explicit padding +fields: + +```c +// C code +#include +struct foo { + uint8_t x; + _Alignas(128) uint8_t y; + uint8_t z; +}; +``` + +```rust +// Rust bindings generated by `bindgen` +#[repr(C, align(128))] +pub struct foo { + pub x: u8, + pub __bindgen_padding_0: [u8; 127usize], + pub y: u8, + pub z: u8, +} +``` + +The `__bindgen_padding_0` field makes the generated bindings more confusing and +less ergonomic. Also, it is unsound: the padding should be using `MaybeUninit`. +And even then, there is no guarantee of ABI compatibility on all potential +targets. + +## Packing values into fewer cache lines + +When working with large values (lookup tables, for example), it is often +desirable, for optimal performance, to pack them into as few cache lines as +possible. One way of doing this is to force the alignment of the value to be at +least the size of the cache line, or perhaps the greatest common denominator of +the value and cache line sizes. + +The simplest way of accomplishing this in Rust today is to use a wrapper struct +with a `#[repr(align(…))]` attribute: + +```rust +type SomeLargeType = [[u8; 64]; 21]; + +#[repr(align(128))] +struct CacheAligned(T); + +static LOOKUP_TABLE: CacheAligned = CacheAligned(SomeLargeType { + data: todo!(), +}); +``` + +However, this approach has several downsides: + +- It requires defining a separate wrapper type. +- It changes the type of the item, which may not be allowed if it is part of the + crate's public API. +- It may add padding to the value, which might not be necessary or desirable. + +In some cases, it can also improve performance to align a function's code in the +same way. + +## Required alignment for low-level use cases + +Some low-level use-cases (for example, the [RISC-V `mtvec` +register](https://five-embeddev.com/riscv-priv-isa-manual/Priv-v1.12/machine.html#machine-trap-vector-base-address-register-mtvec)) +require functions or statics to have a certain minimum alignment. + +## Pointer tagging + +Users may want to specify a minimum alignment for various items, in order to +leave the low bits of pointers to such items free to store additional data. + +## Interoperating with systems that have types where size is not a multiple of alignment + +In Rust, a type’s size is always a multiple of its alignment. However, there are +other languages that can interoperate with Rust, where this is not the case +(WGSL, for example). It’s important for Rust to be able to represent such +structures. + +# Explanation +[explanation]: #explanation + +The `align` attribute is a new inert, built-in attribute that can be applied to +ADT fields, `static` items, function items, and local variable declarations. The +attribute accepts a single required parameter, which must be a power-of-2 +integer literal from 1 up to 229. (This is the same as +`#[repr(align(…))]`.) + +Multiple instances of the `align` attribute may be present on the same item; the +highest alignment among them will be used. The compiler may signal this case +with a warn-by-default lint. + +## On ADT fields + +The `align` attribute may be applied to any field of any `struct`, `enum`, or +`union` that is not `#[repr(transparent)]`. + +```rust +#[repr(C)] +struct Foo { + #[align(8)] + a: u32, +} + +enum Bar { + Variant(#[align(16)] u128), +} + +union Baz { + #[align(16)] + a: u32, +} +``` + +The effect of the attribute is to force the address of the field to have at +least the specified alignment. If the field already has at least that alignment, +due to the required alignment of its type or to a `repr` attribute on the +containing type, the attribute has no effect. + +In contrast to a `repr(align(…))` wrapper struct, an `align` annotation does +*not* necessarily add extra padding to force the field to have a size that is a +multiple of its alignment. (The size of the containing ADT must still be a +multiple of its alignment, which must in turn be no less than that of the +most-aligned field. That hasn't changed.) + +Instances of the `align` attribute for fields of a `#[repr(packed(n))]` ADT may +not specify an alignment higher than `n`. + +```rust +#[repr(packed(4))] +struct Sardines { + #[align(2)] // OK + a: u8, + #[align(4)] // OK + b: u16, + #[align(8)] //~ ERROR + c: u32, +} +``` + +`align` attributes on ADT fields are shown in `rustdoc`-generated documentation. + +## Interaction with `repr(C)` + +`repr(C)` currently has two contradictory meanings: “a simple, linear layout +algorithm that works the same everywhere” and “an ABI matching that of the +target’s standard C compiler”. This RFC does not aim to reslove that conflict; +that is being discussed as part of [RFC +3718](https://github.com/rust-lang/rfcs/pull/3718). Henceforth, we will use +`repr(C_for_real)` to denote “match the system C compiler”, and `repr(linear)` +to denote “simple, portable layout algorithm”; but those names are not +normative. + +### `repr(C_for_real)` + +The layout of a `repr(C_for_real)` ADT with `align` attributes on its fields is +identical to that of the corresponding C ADT declared with `alignas` +annotations. For example, the struct below is equivalent to the C `struct foo` +from the motivation section: + +```rust +#[repr(C_for_real)] +pub struct foo { + pub x: u8, + #[align(128)] + pub y: u8, + pub z: u8, +} +``` + +### `repr(linear)` + +In a `repr(linear)` ADT, a field with an `align` attribute has its alignment, as +well as the alignment of the containing ADT, increased to at least what the +attribute specifies. + +For example, the following two structs have the same layout in memory (though +not necessarily the same ABI): + +```rust +#[repr(linear)] +pub struct foo { + pub x: u8, + #[align(128)] + pub y: u8, + pub z: u8, +} +``` + +```rust +#[repr(linear, align(128))] +pub struct foo2 { + pub x: u8, + pub _padding: [MaybeUninit; 127usize], + pub y: u8, + pub z: u8, +} +``` + +## On `static`s + +Any `static` item (including `static`s inside `extern` blocks) may have an +`align` attribute applied: + +```rust +#[align(32)] +static BAZ: [u32; 12] = [0xDEADBEEF; 12]; + +unsafe extern "C" { + #[align(2)] + safe static BOZZLE: u8; +} +``` + +The effect of the attribute is to force the `static` to be stored with at least +the specified alignment. The attribute does not force padding bytes to be added +after the `static`. For `static`s inside `unsafe extern` blocks, if the `static` +does not meet the specified alignment, the behavior is undefined. (This UB is +analogous to the UB that can result if the static item is not a valid value of +its type. The question of whether the UB can occur even if the item is unused, +has the same answer for both cases.) + +The `align` attribute may also be applied to thread-local `static`s created with +the `thread_local!` macro; the attribute affects the alignment of the underlying +value, not that of the outer `std::thread::LocalKey`. + +```rust +thread_local! { + #[align(64)] + static FOO: u8 = 42; +} + +fn main() { + FOO.with(|r| { + let p: *const u8 = r; + assert_eq!(p.align_offset(64), 0); + }); +} +``` + +`align` attributes on `static`s are shown in `rustdoc`-generated documentation. + +## On function items + +On function items, `#[align(…)]` sets the alignment of the function’s code. (It +does not affect the alignment of its function item type, which remains a 1-ZST.) +This replaces `#[repr(align(…))]` on function items, from +`#![feature(fn_align)]`. + +On `async fn`, the attribute controls the alignment of the code of the function +that returns the `Future`. + +The numerical value of a function pointer to a function with an `#[align(n)]` +attribute is *not* always guaranteed to be a multiple of `n` on all targets. For +example, on 32-bit ARM, the low bit of the function pointer is set for functions +using the Thumb instruction set, even though the actual code of the function is +always aligned to at least 2 bytes. + +`align` attributes on function items are shown in `rustdoc`-generated +documentation. + +## On local variables + +The `align` attribute may also be applied to local variable declarations inside +`let` bindings. The attribute forces the local to have at least the alignment +specified: + +```rust +fn main() { + let (a, #[align(4)] b, #[align(2)] mut c) = (4u8, 2u8, 1u8); + c *= 2; + dbg!(a, b, c); + + if let Some(#[align(4)] x @ 1..) = Some(42u8) { + dbg!(x); + let p: *const u8 = x; + assert_eq!(p.align_offset(4), 0); + } +} +``` + +The `align` attribute may not be applied to function parameters. + +```rust +fn foo(#[align(8)] _a: u32) {} //~ ERROR +``` + +They also may not be applied to `_` bindings. + +```rust +let #[align(4)] _ = true; //~ ERROR +``` + +# Drawbacks +[drawbacks]: #drawbacks + +- This feature adds additional complexity to the languge. +- The distinction between `align` and `repr(align)` may be confusing for users. + +# Rationale and alternatives +[rationale-and-alternatives]: #rationale-and-alternatives + +Compared to the wrapper type approach, the `align` attribute adds additional +flexibility, because it does not force the insertion of padding. If we don't +adopt this feature, `bindgen` will continue to generate suboptimal bindings, and +users will continue to be forced to choose between suboptimal alignment and +additional padding. + +## `#[align(…)]` vs `#[repr(align(…))]` + +One potential alternative would be to use `#[repr(align(…))]` everywhere, +instead of introducing a new attribute. + +Benefits of this alternative: + +- No new attribute polluting the namespace. +- Requesting a certain alignment is spelled the same everywhere. +- `#[repr(…)]` on fields might accept additional options in the future, for + specifying layout and padding more preciesely. +- `#[repr(…)]` on function items could also accept `instruction_set(…)` as an + argument, replacing the existing attribute of that name. + +Drawbacks: + +- `#[repr(align(…))]` is a longer and noisier syntax. +- `#[repr(…)]` on non-ADTs would never accept the same set of options as on + ADTs. On field definitions, it might accept additional options to precisely + control layout; on function items, it might accept `instruction_set(…)`, if we + were to overturn the precedent of that being a standalone attribute. On + statics and local variables, I doubt it would ever accept anything else at + all. +- `#[align(…)]` *only* aligns, while `#[repr(align(…))]` also pads to a multiple + of the alignment. Having different syntax makes that distinction more clear. + +## `#[align(n)]` vs `#[align = n]` + +`align = n` might be misinterpreted as requesting an alignment of *exactly* `n`, +instead of *at least* `n`. + +## `#[align(…)]` on function parameters + +We could choose to allow this. However, this RFC specifies that it should be +rejected, because users might incorrectly think the attribute affects ABI when +it does not. C and C++ make the same choice. + +To give an example of what could go wrong, consider the following function: + +```rust +fn example(#[align(1024)] very_large_value: [u64; 8192]) { + // use `very_large_value` by reference +} +``` + +Calling this function will most likely involve first passing `very_large_value` +on the stack or by pointer, and then copying the entire array to a new place on +the stack in order to align it. This implicit extra stack copy is not present +for `#[align(…)]`ed locals. Forbidding this, and requiring users to make the +move/copy explicit, avoids the performance footgun. + +We could always lift this limitation in the future. + +## Interaction with `async fn` + +This RFC specifies that when applied to `async fn`, the `align` attribute should +affect the alignment of the function that returns the future. This breaks +precedent with `#[inline]`, which affects the alignment of the future `poll` +method. + +There is good reason for this difference. In the case of `inline`, controlling +the inlineability of the function that returns the future is almost never what +you want. That function is mostly trivial, and there is little reason to deviate +from the default of inlining it in most cases. Controlling the inlineability of +the `poll` method is far more useful. + +In contrast, there are several potential reasons to want to control the +alignment of an `async fn`. For example, this could be used in concert with +function pointer tagging schemes. If users apply `#[align(…)]` to an `async fn` +item believing that it will affect the alignment of the function’s pointer (as +it does with any other function item), but instead it affects that of the `poll` +method, that could even result in UB. Therefore, it makes more sense to choose +the simpler and more consistent rule of having the `#[align(…)]` attribute +affect the alignment of the function that returns the future. + +The current `#![feature(fn_align)]` works this way already. + +## `#[align(…)] mut local` via `mut #[align(…)] local` + +This RFC proposes that the `#[align(…)]` attribute should come before the `mut` +keyword when declaring an aligned local variable. + +Local variables are declared with [identifier +patterns](https://doc.rust-lang.org/reference/patterns.html#identifier-patterns). +The local is defined by three pieces of information: + +- Its name, which is declared explicity +- Its mutability, which is declared explicity via the presence or absence of the + `mut` keyword +- Its type, which is derived implicitly from the structure and type of the + surrounding pattern. + +In Rust, attributes come before the element they modify. In this case, the `mut` +keyword is an integral part of the local’s declaration; therefore, the attribute +should precede it. + +# Prior art +[prior-art]: #prior-art + +This proposal is the Rust equivalent of +[C](https://en.cppreference.com/w/c/language/_Alignas_) and +[C++](https://en.cppreference.com/w/cpp/language/alignas) `alignas`. + +There are a few significant semantic differences between those features and this +RFC: + +- `#[align]` additionally allows applying the attribute to function item + declarations, which `alignas` does not permit. +- C++, but not C, allows applying `alignas` to type declarations, like Rust’s + `repr(align)`; this RFC does not permit that usage. +- `alignas(n)` accepts any integer constant expression or type name for `n`; + this RFC accepts only integer literals (for now). +- `alignas(n)` allows `n` to be zero, in which case the specifier is ignored; + this RFC does not permit that usage. + +# Unresolved questions +[unresolved-questions]: #unresolved-questions + +## MSVC + +Does MSVC do something weird with `alignas`? In other words, is the concern +about `repr(C)` vs `repr(linear)` purely theoretical at this point, or does it +matter in practice today? + +## Interaction with `ref`/`ref mut` + +What should the syntax be for applying the `align` attribute to `ref`/`ref mut` +bindings? + + - Option A: the attribute goes inside the `ref`/`ref mut`. + +```rust +fn foo(x: &u8) { + let ref #[align(4)] _a = *x; +} +``` + + - Option B: the attribute goes outside the `ref`/`ref mut`. + +```rust +fn foo(x: &u8) { + let #[align(4)] ref _a = *x; +} +``` + +I believe the simplest option is to forbid this combination entirely for now, +especially as there is effectively no use-case for it. + +### How I believe we should decide this eventually + +In my view, the resolution of this question hinges on whether `ref`/`ref mut` +are an integral part of the local declaration. My instinct is to say that they +are not. Like the rest of the surrounding pattern, they describe how the initial +value of the local should be extracted from the scrutinee. Like this surrounding +pattern, they implicitly affect the local’s type, but don’t otherwise affect its +properties. At the point of use, you can’t distinguish a local declared with +`ref`/`ref mut` from one declared some other way. + +However, one could argue that `ref`/`ref mut` are part of the same “binding +mode” syntactic element as `mut`, and that therefore, if the `align` attribute +precedes `mut`, it should precede `ref`/`ref mut` also. + +I believe the the correct time to resolve this question will be when we decide +on a syntax for combining `ref`/`ref mut` with `mut`. If we choose a syntax that +make it clear that these are distinct elements: + +```rust +let ref (mut x) = …; +let ref mut (mut x) = …; +``` + +Then, that would imply that `#[align(…)]` should be applied just before the `mut`: + +```rust +let ref #[align(…)] x = …; +let ref mut #[align(…)] x = …; +let ref #[align(…)] mut x = …; +let ref mut #[align(…)] mut x = …; +``` + +But if we choose a syntax that treats tem as components of a single “binding +mode” element: + +```rust +let mut ref x = …; +let mut ref mut x = …; +``` + +Then, `#[align]` should always precede that element: + +```rust +let #[align(…)] ref x = …; +let #[align(…)] ref mut x = …; +let #[align(…)] mut ref x = …; +let #[align(…)] mut ref mut x = …; +``` + +# Future possibilities +[future-possibilities]: #future-possibilities + +- The `align(…)` and `repr(align(…))` attributes currently accept only integer + literals as parameters. In the future, they could support `const` expressions + as well. +- We could provide additional facilities for controlling the layout of ADTs; for + example, a way to specify exact field offsets or arbitrary padding. +- We could add type-safe APIs for over-aligned pointers; for example, + over-aligned reference types that are subtypes of `&`/`&mut`. +- We could also add similar APIs for over-aligned function pointers. +- We could loosen the restriction that fields of a `packed(n)` struct cannot + specify an alignment greater that `n`. (Apparently, some C compilers allow + something similar.) +- Once + [`#![feature(stmt_expr_attributes)]`](https://github.com/rust-lang/rust/issues/15701) + is stable, we could allow applying `#![align(…))]` to closures and async + blocks as well.