Skip to content

Commit e097e1b

Browse files
authored
Merge pull request rust-lang#2243 from DanielEScherzer/type-layout-algo
type-layout: rewrite `#[repr(C)]` struct layout algorithm
2 parents f3627a2 + e000ac9 commit e097e1b

1 file changed

Lines changed: 95 additions & 30 deletions

File tree

src/type-layout.md

Lines changed: 95 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -209,43 +209,108 @@ For each field in declaration order in the struct, first determine the size and
209209

210210
Finally, the size of the struct is the current offset rounded up to the nearest multiple of the struct's alignment.
211211

212-
Here is this algorithm described in pseudocode.
213-
214-
<!-- ignore: pseudocode -->
215-
```rust,ignore
216-
/// Returns the amount of padding needed after `offset` to ensure that the
217-
/// following address will be aligned to `alignment`.
218-
fn padding_needed_for(offset: usize, alignment: usize) -> usize {
219-
let misalignment = offset % alignment;
220-
if misalignment > 0 {
221-
// round up to next multiple of `alignment`
222-
alignment - misalignment
223-
} else {
224-
// already a multiple of `alignment`
225-
0
226-
}
227-
}
228-
229-
struct.alignment = struct.fields().map(|field| field.alignment).max();
212+
Here is the algorithm:
230213

231-
let current_offset = 0;
232-
233-
for field in struct.fields_in_declaration_order() {
234-
// Increase the current offset so that it's a multiple of the alignment
235-
// of this field. For the first field, this will always be zero.
236-
// The skipped bytes are called padding bytes.
237-
current_offset += padding_needed_for(current_offset, field.alignment);
214+
```rust
215+
# /// A field of a struct.
216+
# #[derive(Debug)]
217+
struct Field {
218+
alignment: usize,
219+
size: usize,
220+
}
221+
# /// Layout of user-defined structs.
222+
# #[derive(Debug)]
223+
struct MockLayout {
224+
# /// Fields stored in declaration order.
225+
fields: Vec<Field>,
226+
# /// Offset of each field from the start of the struct.
227+
field_offsets: Vec<usize>,
228+
# /// Overall alignment.
229+
alignment: usize,
230+
# /// Overall size.
231+
size: usize,
232+
}
238233

239-
struct[field].offset = current_offset;
234+
impl MockLayout {
235+
/// Returns the amount of padding needed after `offset` to ensure that the
236+
/// following address will be aligned to `alignment`.
237+
fn padding_needed_for(offset: usize, alignment: usize) -> usize {
238+
let misalignment = offset % alignment;
239+
if misalignment > 0 {
240+
// Round up to next multiple of `alignment`.
241+
alignment - misalignment
242+
} else {
243+
// Already a multiple of `alignment`.
244+
0
245+
}
246+
}
240247

241-
current_offset += field.size;
248+
/// Fields must be in declaration order. By this point, they have already
249+
/// had their alignments and sizes calculated.
250+
pub fn from_fields(fields: Vec<Field>) -> Self {
251+
// "The alignment of the struct is the alignment of the most-aligned
252+
// field in it, or one if there are no fields."
253+
let alignment = fields
254+
.iter()
255+
.map(|field| field.alignment)
256+
.max()
257+
.unwrap_or(1);
258+
259+
// "Start with a current offset of 0 bytes."
260+
let mut current_offset = 0;
261+
262+
let mut field_offsets = vec![];
263+
for field in &fields {
264+
// "If the current offset is not a multiple of the field's
265+
// alignment, then add padding bytes to the current offset until it
266+
// is a multiple of the field's alignment."
267+
current_offset += Self::padding_needed_for(
268+
current_offset,
269+
field.alignment
270+
);
271+
272+
// "The offset for the field is what the current offset is now."
273+
field_offsets.push(current_offset);
274+
275+
// "Then increase the current offset by the size of the field."
276+
current_offset += field.size;
277+
}
278+
279+
// "Finally, the size of the struct is the current offset rounded up to
280+
// the nearest multiple of the struct's alignment."
281+
let size = current_offset + Self::padding_needed_for(
282+
current_offset,
283+
alignment
284+
);
285+
286+
MockLayout { fields, field_offsets, alignment, size }
287+
}
242288
}
243-
244-
struct.size = current_offset + padding_needed_for(current_offset, struct.alignment);
289+
#
290+
# #[repr(C)]
291+
# struct Demo {
292+
# first: u8,
293+
# second: u32,
294+
# third: u64,
295+
# }
296+
# macro_rules! fields {
297+
# ( $( $t:ty ),+ ) => {
298+
# vec![
299+
# $( Field {
300+
# alignment: std::mem::align_of::<$t>(),
301+
# size: std::mem::size_of::<$t>(),
302+
# }),+
303+
# ]
304+
# }
305+
# }
306+
# let fields = fields![u8, u32, u64];
307+
# let demo_layout = MockLayout::from_fields(fields);
308+
# assert_eq!(std::mem::align_of::<Demo>(), demo_layout.alignment);
309+
# assert_eq!(std::mem::size_of::<Demo>(), demo_layout.size);
245310
```
246311

247312
> [!WARNING]
248-
> This pseudocode uses a naive algorithm that ignores overflow issues for the sake of clarity. To perform memory layout computations in actual code, use [`Layout`].
313+
> This mock implementation uses a naive algorithm that ignores overflow issues for the sake of clarity. To perform memory layout computations in actual code, use [`Layout`].
249314
250315
> [!NOTE]
251316
> This algorithm can produce [zero-sized] structs. In C, an empty struct declaration like `struct Foo { }` is illegal. However, both gcc and clang support options to enable such structs, and assign them size zero. C++, in contrast, gives empty structs a size of 1, unless they are inherited from or they are fields that have the `[[no_unique_address]]` attribute, in which case they do not increase the overall size of the struct.

0 commit comments

Comments
 (0)