-
Notifications
You must be signed in to change notification settings - Fork 1.6k
RFC: Add an attribute for raising the alignment of various items #3806
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?
Changes from 1 commit
3a4f5ac
4badbc9
2b647a8
13cc4f2
bbe09ee
81748b6
51b8069
855a766
1c7a402
dee1e70
4701919
59b1230
16f3bee
5229770
96350ee
0c21fb3
959a94c
75f615a
8b1fa67
95f3972
fefe945
a478717
32b2a93
f140b23
60f23d2
3351aa9
e10fe79
441e05b
8693706
42fb9e2
9f5fb37
553b5ef
73973de
4688f11
9efcb25
7c155c8
45a9d3d
893fd65
5827b39
5da3dcb
42f111b
e79807a
d66a5cd
f16a88e
0b02d6d
2ec537f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I must also note that I have voiced, repeatedly, that attributes do not compose very well in Rust compared to things more deeply embedded in the type, and the idea that this will compose as well as it does in C seems doubtful. I have made a separate note in one case of such. Nonetheless it is obviously the case that there are many positions where this attribute is desirable to have but there is no real way to introduce it to the type, or such is not reasonable. I wish this had been more incremental, in starting with such cases first. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This does, actually, seem like it's part of the unnameable type of the function itself, and that really this should be carried into the type of function pointers derived from it. The interaction of this with traits and refinement makes this more apparent. One could imagine, e.g., the function type implementing a trait like There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's notable as well, in light of other recent discussions, that inverse marker effects -- i.e., constraining properties of functions -- can in theory be modeled with traits. Probably alignment isn't what we'd think of as an (inverse) effect -- it's not (the absence of) a side effect of the function that exceeds its signature -- but if we squint, it does maybe have some modeling similarities. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I have hoped for some time to be able to implement instead a trait like There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Added to the future possibilities. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Exposing the chosen alignment as a property of the fn item type seems like a sensible extension to me (though it pokes a small hole in the arguments against I'm more worried about putting this information into fn pointer types. Those already have a lot of dimensions (safe vs unsafe, calling convention, lifetimes, and probably I'm also not sure if changing the "canonical" fn pointer type for a fn item type would be a breaking change (beyond new possibilities for inference failure). If it is, then figuring out the details about "aligned fn pointer types" might be a blocker for stabilizing There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's true, we usually prefer to lean on coercions instead of subtyping for things like function pointers but that can have problems due to having coercions select different implementations for the same item according to fairly subtle rules. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not just due to which coercion is chosen, but also because coercions don't help when the type that needs coercion is behind any sort of type constructor (including very basic ones like |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,377 @@ | ||
| - 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 <stdint.h> | ||
| 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 | ||
| platforms. | ||
|
|
||
| ## 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>(T); | ||
|
|
||
| static LOOKUP_TABLE: CacheAligned<SomeLargeType> = 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. | ||
|
Comment on lines
+73
to
+74
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ehn. Technically true but Deref implementations exist, which allows one to often escape this breakage since naming a static typically makes it a subexpression subject to Deref coercions... I wonder if there's a case where that doesn't apply? Maybe. |
||
| - It may add padding to the value, which might not be necessary or desirable. | ||
|
|
||
| In some cases, it can also improve performance to align function items 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. | ||
|
|
||
| ## 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 | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please follow the template in https://github.com/rust-lang/rfcs/blob/master/0000-template.md and have both guide-level (how I'd explain it to someone) and reference-level (all the gory details of exactly what the operational behaviour is) parts. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The “Explanation” section is intended to be reference-level. This is a super niche/advanced feature; it is all gory details. Therefore, I don’t feel a separate guide-level explanation would be worthwhile. |
||
| [explanation]: #explanation | ||
|
|
||
| The `align` attribute is a new inert, built-in attribute that can be applied to | ||
Jules-Bertholet marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| 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 2<sup>29</sup>. (This is the same as | ||
| `#[repr(align(…))]`.) | ||
|
||
|
|
||
| Multiple `align` attributes 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)]`. | ||
Jules-Bertholet marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| ```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; that hasn't changed.) | ||
Jules-Bertholet marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| `align` attributes 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, | ||
| } | ||
Jules-Bertholet marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| ``` | ||
|
|
||
| `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<u8>; 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. (For | ||
| misaligned `static` items declared inside old-style `extern` blocks, UB occurs | ||
| only if the item is used.) | ||
Jules-Bertholet marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| 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. This | ||
| replaces `#[repr(align(…))]` on function items from `#![feature(fn_align)]`. | ||
Jules-Bertholet marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| `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); | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| `align` attributes may not be applied to function parameters. | ||
|
|
||
| ```rust | ||
| fn foo(#[align(8)] _a: u32) {} //~ ERROR | ||
| ``` | ||
Jules-Bertholet marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| 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. | ||
|
|
||
| Drawbacks: | ||
|
|
||
| - `#[repr(align(…))]` is a longer and noisier syntax. | ||
| - `#[repr(…)]` on non-ADTs, with the possible exception of field definitions, will | ||
| probably only ever accept `align(…)` as an argument. It would not be consistent | ||
| with the existing `#[repr(…)]` on ADTs. | ||
| - `#[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`. | ||
|
|
||
| # Prior art | ||
| [prior-art]: #prior-art | ||
|
|
||
| This proposal is the Rust equivalent of [C | ||
| `alignas`](https://en.cppreference.com/w/c/language/_Alignas_). | ||
|
|
||
| # Unresolved questions | ||
| [unresolved-questions]: #unresolved-questions | ||
|
|
||
| 1. 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.) | ||
|
|
||
| 2. 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? | ||
|
|
||
| # 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. | ||
|
Comment on lines
+597
to
+599
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I... do not expect this to be a realistic possibility. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. C++ allows it There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. put differently: there is no precedent in rust for this sort of const evaluation in attributes, and it really does not fit into the current attribute architecture. But "future possibilities" don't really need to think too much about (short-term) feasibility I think. |
||
| - 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. | ||
Uh oh!
There was an error while loading. Please reload this page.
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 have no objection per se to the introduction of
#[align]syntactically but I feel this will have a number of surprising complexities that will equate to the breaking change ofsize != stride. It feels like this tries to persuade the reader that hiding this break of assumptions in an attribute that doesn't start with#[reprwill render it harmless. If you point at something withrepr(align(N)), you are guaranteed padding that you can freely clobber, for instance, but pointers to things with this attribute have no such guarantee. This feels very likely to makeunsafecode more confusing to write, in a similar way to what has driven us to say that we will not breaksize == stride.Uh oh!
There was an error while loading. Please reload this page.
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.
Yes, that is the point. If you need the guarantee, use a
#[repr(align)]wrapper type. References to#[align]things have the same type as any other reference, because they provide the same guarantees as any other reference.