Skip to content

Commit 7a779d9

Browse files
type-layout: rewrite #[repr(C)] struct layout algorithm
Make the code actual compile by defining types, not using a variable named `struct`, adding `mut` where needed, etc. Properly handle the `max()` alignment call returning `Option<usize>` rather than `usize` by using 0 for the alignment if there are no fields. Add comments with the parts of the algorithm that are represented by the statements.
1 parent 8c88f9d commit 7a779d9

1 file changed

Lines changed: 71 additions & 30 deletions

File tree

src/type-layout.md

Lines changed: 71 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -209,43 +209,84 @@ 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 this algorithm with some example code.
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+
# struct Field {
217+
# alignment: usize,
218+
# size: usize,
219+
# }
220+
# /// User-defined structs
221+
struct UserStruct {
222+
# /// Fields stored in declaration order
223+
fields: Vec<Field>,
224+
# /// Offset of each field from the start of the struct
225+
field_offsets: Vec<usize>,
226+
# /// Overall alignment
227+
alignment: usize,
228+
# /// Overall size
229+
size: usize,
230+
}
238231

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

241-
current_offset += field.size;
246+
/// Fields must be in declaration order!
247+
/// By this point, they have already had their alignments and sizes calculated.
248+
pub fn from_fields(fields: Vec<Field>) -> Self {
249+
// "The alignment of the struct is the alignment of the most-aligned
250+
// field in it."
251+
let max_alignment = fields.iter().map(|field| field.alignment).max();
252+
// max_alignment is None iff there are no fields, in which case the
253+
// overall alignment is 1 (the minimum).
254+
let alignment = max_alignment.unwrap_or(1);
255+
256+
// "Start with a current offset of 0 bytes."
257+
let mut current_offset = 0;
258+
259+
let mut field_offsets = vec![];
260+
for field in &fields {
261+
// "If the current offset is not a multiple of the field's alignment,
262+
// then add padding bytes to the current offset until it is a multiple
263+
// of the field's alignment."
264+
current_offset += Self::padding_needed_for(
265+
current_offset,
266+
field.alignment
267+
);
268+
269+
// "The offset for the field is what the current offset is now."
270+
field_offsets.push(current_offset);
271+
272+
// "Then increase the current offset by the size of the field."
273+
current_offset += field.size;
274+
}
275+
276+
// "Finally, the size of the struct is the current offset rounded up to
277+
// the nearest multiple of the struct's alignment."
278+
let size = current_offset + Self::padding_needed_for(
279+
current_offset,
280+
alignment
281+
);
282+
283+
UserStruct { fields, field_offsets, alignment, size }
284+
}
242285
}
243-
244-
struct.size = current_offset + padding_needed_for(current_offset, struct.alignment);
245286
```
246287

247288
> [!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`].
289+
> 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`].
249290
250291
> [!NOTE]
251292
> 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)