Skip to content

Commit 75d8fc6

Browse files
Add align_attr RFC
1 parent 3a35580 commit 75d8fc6

File tree

1 file changed

+377
-0
lines changed

1 file changed

+377
-0
lines changed

text/3805-align-attr.md

+377
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,377 @@
1+
- Feature Name: `align_attr`
2+
- Start Date: 2025-05-01
3+
- RFC PR: [rust-lang/rfcs#3805](https://github.com/rust-lang/rfcs/pull/3805)
4+
- Rust Issue: [rust-lang/rust#0000](https://github.com/rust-lang/rust/issues/0000)
5+
6+
# Summary
7+
[summary]: #summary
8+
9+
Add an `#[align(…)]` attribute to set the minimum alignment of `struct` and `enum`
10+
fields, `static`s, functions, and local variables.
11+
12+
# Motivation
13+
[motivation]: #motivation
14+
15+
## Bindings to C and C++
16+
17+
[C](https://en.cppreference.com/w/c/language/_Alignas) and [C++](https://en.cppreference.com/w/cpp/language/alignas)
18+
provide an `alignas` modifier to set the alignment of specific struct fields. To
19+
represent such structures in Rust, `bindgen` is sometimes forced to add explicit
20+
padding fields:
21+
22+
```c
23+
// C code
24+
#include <stdint.h>
25+
struct foo {
26+
uint8_t x;
27+
_Alignas(128) uint8_t y;
28+
uint8_t z;
29+
};
30+
```
31+
32+
```rust
33+
// Rust bindings generated by `bindgen`
34+
#[repr(C, align(128))]
35+
pub struct foo {
36+
pub x: u8,
37+
pub __bindgen_padding_0: [u8; 127usize],
38+
pub y: u8,
39+
pub z: u8,
40+
}
41+
```
42+
43+
The `__bindgen_padding_0` field makes the generated bindings more confusing and
44+
less ergonomic. Also, it is unsound: the padding should be using `MaybeUninit`.
45+
And even then, there is no guarantee of ABI compatibility on all potential
46+
platforms.
47+
48+
## Packing values into fewer cache lines
49+
50+
When working with large values (lookup tables, for example), it is often
51+
desirable, for optimal performance, to pack them into as few cache lines as
52+
possible. One way of doing this is to force the alignment of the value to be at
53+
least the size of the cache line, or perhaps the greatest common denominator of
54+
the value and cache line sizes.
55+
56+
The simplest way of accomplishing this in Rust today is to use a wrapper struct
57+
with a `#[repr(align(…))]` attribute:
58+
59+
```rust
60+
type SomeLargeType = [[u8; 64]; 21];
61+
62+
#[repr(align(128))]
63+
struct CacheAligned<T>(T);
64+
65+
static LOOKUP_TABLE: CacheAligned<SomeLargeType> = CacheAligned(SomeLargeType {
66+
data: todo!(),
67+
});
68+
```
69+
70+
However, this approach has several downsides:
71+
72+
- It requires defining a separate wrapper type.
73+
- It changes the type of the item, which may not be allowed if it is part of the
74+
crate's public API.
75+
- It may add padding to the value, which might not be necessary or desirable.
76+
77+
In some cases, it can also improve performance to align function items in the
78+
same way.
79+
80+
## Required alignment for low-level use cases
81+
82+
Some low-level use-cases (for example, the [RISC-V `mtvec`
83+
register](https://five-embeddev.com/riscv-priv-isa-manual/Priv-v1.12/machine.html#machine-trap-vector-base-address-register-mtvec))
84+
require functions or statics to have a certain minimum alignment.
85+
86+
## Interoperating with systems that have types where size is not a multiple of alignment
87+
88+
In Rust, a type’s size is always a multiple of its alignment. However, there are
89+
other languages that can interoperate with Rust, where this is not the case
90+
(WGSL, for example). It’s important for Rust to be able to represent such
91+
structures.
92+
93+
# Explanation
94+
[explanation]: #explanation
95+
96+
The `align` attribute is a new inert, built-in attribute that can be applied to
97+
ADT fields, `static` items, function items, and local variable declarations. The
98+
attribute accepts a single required parameter, which must be a power-of-2
99+
integer literal from 1 up to 2<sup>29</sup>. (This is the same as
100+
`#[repr(align(…))]`.)
101+
102+
Multiple `align` attributes may be present on the same item; the highest
103+
alignment among them will be used. The compiler may signal this case with a
104+
warn-by-default lint.
105+
106+
## On ADT fields
107+
108+
The `align` attribute may be applied to any field of any `struct`, `enum`, or
109+
`union` that is not `#[repr(transparent)]`.
110+
111+
```rust
112+
#[repr(C)]
113+
struct Foo {
114+
#[align(8)]
115+
a: u32,
116+
}
117+
118+
enum Bar {
119+
Variant(#[align(16)] u128),
120+
}
121+
122+
union Baz {
123+
#[align(16)]
124+
a: u32,
125+
}
126+
```
127+
128+
The effect of the attribute is to force the address of the field to have at
129+
least the specified alignment. If the field already has at least that
130+
alignment, due to the required alignment of its type or to a `repr` attribute on
131+
the containing type, the attribute has no effect.
132+
133+
In contrast to a `repr(align(…))` wrapper struct, an `align` annotation does *not*
134+
necessarily add extra padding to force the field to have a size that is a
135+
multiple of its alignment. (The size of the containing ADT must still be a
136+
multiple of its alignment; that hasn't changed.)
137+
138+
`align` attributes for fields of a `#[repr(packed(n))]` ADT may not specify an
139+
alignment higher than `n`.
140+
141+
```rust
142+
#[repr(packed(4))]
143+
struct Sardines {
144+
#[align(2)] // OK
145+
a: u8,
146+
#[align(4)] // OK
147+
b: u16,
148+
#[align(8)] //~ ERROR
149+
c: u32,
150+
}
151+
```
152+
153+
`align()` attributes on ADT fields are shown in `rustdoc`-generated documentation.
154+
155+
## Interaction with `repr(C)`
156+
157+
`repr(C)` currently has two contradictory meanings: “a simple, linear layout
158+
algorithm that works the same everywhere” and “an ABI matching that of the
159+
target’s standard C compiler”. This RFC does not aim to reslove that conflict;
160+
that is being discussed as part of [RFC
161+
3718](https://github.com/rust-lang/rfcs/pull/3718). Henceforth, we will use
162+
`repr(C_for_real)` to denote “match the system C compiler”, and `repr(linear)`
163+
to denote “simple, portable layout algorithm”; but those names are not
164+
normative.
165+
166+
### `repr(C_for_real)`
167+
168+
The layout of a `repr(C_for_real)` ADT with `align` attributes on its fields is
169+
identical to that of the corresponding C ADT declared with `alignas`
170+
annotations. For example, the struct below is equivalent to the C `struct foo`
171+
from the motivation section:
172+
173+
```rust
174+
#[repr(C_for_real)]
175+
pub struct foo {
176+
pub x: u8,
177+
#[align(128)]
178+
pub y: u8,
179+
pub z: u8,
180+
}
181+
```
182+
183+
### `repr(linear)`
184+
185+
In a `repr(linear)` ADT, a field with an `align` attribute has its alignment, as
186+
well as the alignment of the containing ADT, increased to at least what the
187+
attribute specifies.
188+
189+
For example, the following two structs have the same layout in memory (though
190+
not necessarily the same ABI):
191+
192+
```rust
193+
#[repr(linear)]
194+
pub struct foo {
195+
pub x: u8,
196+
#[align(128)]
197+
pub y: u8,
198+
pub z: u8,
199+
}
200+
```
201+
202+
```rust
203+
#[repr(linear, align(128))]
204+
pub struct foo2 {
205+
pub x: u8,
206+
pub _padding: [MaybeUninit<u8>; 127usize],
207+
pub y: u8,
208+
pub z: u8,
209+
}
210+
```
211+
212+
## On `static`s
213+
214+
Any `static` item (including `static`s inside `extern` blocks) may have an
215+
`align` attribute applied:
216+
217+
```rust
218+
#[align(32)]
219+
static BAZ: [u32; 12] = [0xDEADBEEF; 12];
220+
221+
unsafe extern "C" {
222+
#[align(2)]
223+
safe static BOZZLE: u8;
224+
}
225+
```
226+
227+
The effect of the attribute is to force the `static` to be stored with at least
228+
the specified alignment. The attribute does not force padding bytes to be added
229+
after the `static`. For `static`s inside `unsafe extern` blocks, if the `static`
230+
does not meet the specified alignment, the behavior is undefined. (For
231+
misaligned `static` items declared inside old-style `extern` blocks, UB occurs
232+
only if the item is used.)
233+
234+
The `align` attribute may also be applied to thread-local `static`s created with
235+
the `thread_local!` macro; the attribute affects the alignment of the underlying
236+
value, not that of the outer `std::thread::LocalKey`.
237+
238+
```rust
239+
thread_local! {
240+
#[align(64)]
241+
static FOO: u8 = 42;
242+
}
243+
244+
fn main() {
245+
FOO.with(|r| {
246+
let p: *const u8 = r;
247+
assert_eq!(p.align_offset(64), 0);
248+
});
249+
}
250+
```
251+
252+
`align()` attributes on `static`s are shown in `rustdoc`-generated documentation.
253+
254+
## On function items
255+
256+
On function items, `#[align(…)]` sets the alignment of the function’s code. This
257+
replaces `#[repr(align(…))]` on function items from `#![feature(fn_align)]`.
258+
259+
`align` attributes on function items are shown in `rustdoc`-generated documentation.
260+
261+
## On local variables
262+
263+
The `align` attribute may also be applied to local variable declarations inside
264+
`let` bindings. The attribute forces the local to have at least the alignment
265+
specified:
266+
267+
```rust
268+
fn main() {
269+
let (a, #[align(4)] b, #[align(2)] mut c) = (4u8, 2u8, 1u8);
270+
c *= 2;
271+
dbg!(a, b, c);
272+
273+
if let Some(#[align(4)] x @ 1..) = Some(42u8) {
274+
dbg!(x);
275+
let p: *const u8 = x;
276+
assert_eq!(p.align_offset(4), 0);
277+
}
278+
}
279+
```
280+
281+
`align` attributes may not be applied to function parameters.
282+
283+
```rust
284+
fn foo(#[align(8)] _a: u32) {} //~ ERROR
285+
```
286+
287+
They also may not be applied to `_` bindings.
288+
289+
```rust
290+
let #[align(4)] _ = true; //~ ERROR
291+
```
292+
293+
# Drawbacks
294+
[drawbacks]: #drawbacks
295+
296+
- This feature adds additional complexity to the languge.
297+
- The distinction between `align` and `repr(align)` may be confusing for users.
298+
299+
# Rationale and alternatives
300+
[rationale-and-alternatives]: #rationale-and-alternatives
301+
302+
Compared to the wrapper type approach, the `align` attribute adds additional
303+
flexibility, because it does not force the insertion of padding. If we don't
304+
adopt this feature, `bindgen` will continue to generate suboptimal bindings, and
305+
users will continue to be forced to choose between suboptimal alignment and
306+
additional padding.
307+
308+
## `#[align(…)]` vs `#[repr(align(…))]`
309+
310+
One potential alternative would be to use `#[repr(align(…))]` everywhere,
311+
instead of introducing a new attribute.
312+
313+
Benefits of this alternative:
314+
315+
- No new attribute polluting the namespace.
316+
- Requesting a certain alignment is spelled the same everywhere.
317+
- `#[repr(…)]` on fields might accept additional options in the future.
318+
319+
Drawbacks:
320+
321+
- `#[repr(align(…))]` is a longer and noisier syntax.
322+
- `#[repr(…)]` on non-ADTs, with the possible exception of field definitions, will
323+
probably only ever accept `align(…)` as an argument. It would not be consistent
324+
with the existing `#[repr(…)]` on ADTs.
325+
- `#[align(…)]` *only* aligns, while `#[repr(align(…))]` also pads to a multiple
326+
of the alignment. Having different syntax makes that distinction more clear.
327+
328+
## `#[align(n)]` vs `#[align = n]`
329+
330+
`align = n` might be misinterpreted as requesting an alignment of *exactly* `n`,
331+
instead of *at least* `n`.
332+
333+
# Prior art
334+
[prior-art]: #prior-art
335+
336+
This proposal is the Rust equivalent of [C
337+
`alignas`](https://en.cppreference.com/w/c/language/_Alignas_).
338+
339+
# Unresolved questions
340+
[unresolved-questions]: #unresolved-questions
341+
342+
1. What should the syntax be for applying the `align` attribute to `ref`/`ref
343+
mut` bindings?
344+
345+
- Option A: the attribute goes inside the `ref`/`ref mut`.
346+
347+
```rust
348+
fn foo(x: &u8) {
349+
let ref #[align(4)] _a = *x;
350+
}
351+
```
352+
353+
- Option B: the attribute goes outside the `ref`/`ref mut`.
354+
355+
```rust
356+
fn foo(x: &u8) {
357+
let #[align(4)] ref _a = *x;
358+
}
359+
```
360+
361+
(I believe the simplest option is to forbid this combination entirely for now.)
362+
363+
2. Does MSVC do something weird with `alignas`? In other words, is the concern
364+
about `repr(C)` vs `repr(linear)` purely theoretical at this point, or does
365+
it matter in practice today?
366+
367+
# Future possibilities
368+
[future-possibilities]: #future-possibilities
369+
370+
- The `align(…)` and `repr(align(…))` attributes currently accept only integer
371+
literals as parameters. In the future, they could support `const` expressions
372+
as well.
373+
- We could provide additional facilities for controlling the layout of ADTs; for
374+
example, a way to specify exact field offsets or arbitrary padding.
375+
- We could add type-safe APIs for over-aligned pointers; for example,
376+
over-aligned reference types that are subtypes of `&`/`&mut`.
377+
- We could also add similar APIs for over-aligned function pointers.

0 commit comments

Comments
 (0)