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