Summary
Add an #[align]
attribute to set the minimum alignment of struct
and enum
fields, static
s, and local variables.
Motivation
Bindings to C and C++
C and C++
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 code
#include <stdint.h>
struct foo {
uint8_t x;
_Alignas(128) uint8_t y;
uint8_t z;
};
// Rust bindings generated by `bindgen`
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.
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:
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.
- It may add padding to the value, which might not be necessary or desirable.
Explanation
The align
attribute is a new inert, built-in attribute that can be applied to
ADT fields, static
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)
.)
On ADT fields
The align
attribute may be applied to any field of any struct
, enum
, or union
that is
not #[repr(transparent)]
.
#[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.)
The layout of a repr(C)
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:
#[repr(C)]
pub struct foo {
pub x: u8,
#[align(128)]
pub y: u8,
pub z: u8,
}
align
attributes for fields of a #[repr(packed(n))]
ADT may not specify an alignment higher than n
.
#[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.
On static
s
Any static
item (including static
s inside extern
blocks) may have an
align
attribute applied:
#[align(32)]
static BAZ: [u32; 12] = [0xDEADBEEF; 12];
// RFC 3484 syntax
// (the attibute works with the legacy syntax as well)
unsafe extern "C" {
#[align(2)]
safe static BOZZLE: u8;
}
As before, multiple attributes may be applied to the same item; only the largest one will be considered.
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 extern
blocks, if the static
does
not meet the specified alignment, the behavior is undefined.
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
.
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 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:
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);
}
}
As before, multiple attributes may be applied to the same local; only the largest one will be considered.
align
attributes may not be applied to function parameters.
fn foo(#[align(8)] _a: u32) {} //~ ERROR
They also may not be applied to _
bindings.
let #[align(4)] _ = true; //~ ERROR
Drawbacks
- This feature adds additional complexity to the languge.
- The distinction between
align
andrepr(align)
may be confusing for users.
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.
Prior art
This proposal is the Rust equivalent of C alignas
.
Unresolved questions
- What should the syntax be for applying the
align
attribute toref
/ref mut
bindings?
- Option A: the attribute goes inside the
ref
/ref mut
.
fn foo(x: &u8) {
let ref #[align(4)] _a = *x;
}
- Option B: the attribute goes outside the
ref
/ref mut
.
fn foo(x: &u8) {
let #[align(4)] ref _a = *x;
}
- Does MSVC do something weird with
alignas
?
Future possibilities
- The
align
andrepr(align)
attributes currently accept only integer literals as parameters. In the future, they could supportconst
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
.