Skip to content
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

rustc_codegen_llvm: don't assume offsets are always aligned. #53998

Merged
merged 1 commit into from
Sep 9, 2018

Conversation

eddyb
Copy link
Member

@eddyb eddyb commented Sep 6, 2018

Fixes #53728 by taking into account not just overall type alignment and the field's alignment when determining whether a field is aligned or not ("packed"), but also the field's offset within the type.

Previously, rustc assumed that the offset was always at least as aligned as min(struct.align, field.align). However, there's no real reason to have that assumption, and it obviously can't always be true after we implement #[repr(align(N), pack(K))]. There's also a case today where that assumption is not true, involving niche discriminants in enums:

Suppose that we have the code in #53728:

#[repr(u16)]
enum DeviceKind {
    Nil = 0,
}

#[repr(packed)]
struct DeviceInfo {
    endianness: u8,
    device_kind: DeviceKind,
}

struct Wrapper {
    device_info: DeviceInfo,
    data: u32
}

Observe the layout of Option<Wrapper>. It has an alignment of 4 because of the u32. device_info.device_kind is a good niche field to use, which means the enum ends up with this layout:

size = 8
align = 4
fields = [
    { offset=1, type=u16 } // discriminant, .<Some>.device_info.device_kind
]

And here we have an discriminant with alignment 2 (u16) but offset 1.

@rust-highfive
Copy link
Collaborator

r? @oli-obk

(rust_highfive has picked a reviewer for you, use r? to override)

@rust-highfive rust-highfive added the S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. label Sep 6, 2018
@@ -174,7 +174,12 @@ impl PlaceRef<'ll, 'tcx> {
let cx = bx.cx;
let field = self.layout.field(cx, ix);
let offset = self.layout.fields.offset(ix);
let align = self.align.min(self.layout.align).min(field.align);
let mut real_field_align = self.align.min(self.layout.align).min(field.align);
if offset.abi_align(real_field_align) > offset {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since this code is already duplicated twice in rustc and will get a third duplication in miri, I think this warrants a method on Align taking a field alignment and a field offset and producing the real alignment.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's not enough, sadly, unless you mean starting from self.align.min(self.layout.align) and then using the method on that. I think I like that, and I can fix the FIXME too.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes that's what I meant.

@eddyb
Copy link
Member Author

eddyb commented Sep 6, 2018

@oli-obk Does this new approach work for you?

@oli-obk
Copy link
Contributor

oli-obk commented Sep 6, 2018

@bors r+ the math checks out, yes

@bors
Copy link
Contributor

bors commented Sep 6, 2018

📌 Commit b9e7574 has been approved by oli-obk

@bors bors added S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Sep 6, 2018
@@ -174,7 +174,10 @@ impl PlaceRef<'ll, 'tcx> {
let cx = bx.cx;
let field = self.layout.field(cx, ix);
let offset = self.layout.fields.offset(ix);
let align = self.align.min(self.layout.align).min(field.align);
let effective_field_align = self.align
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why isn't field.align set up properly here?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok.

let align = self.align.min(self.layout.align).min(field.align);
let effective_field_align = self.align
.min(self.layout.align)
.min(field.align)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might help #54028 to remove this line, and rely strictly on the alignment of the type, combined with the best-case alignment for the offset. That way, we can hint LLVM about alignments that are higher than it can figure out by itself. Do we want to do that and do we expect a performance improvement?

Mark-Simulacrum added a commit to Mark-Simulacrum/rust that referenced this pull request Sep 8, 2018
rustc_codegen_llvm: don't assume offsets are always aligned.

Fixes rust-lang#53728 by taking into account not just overall type alignment and the field's alignment when determining whether a field is aligned or not ("packed"), but also the field's offset within the type.

Previously, rustc assumed that the offset was always at least as aligned as `min(struct.align, field.align)`. However, there's no real reason to have that assumption, and it obviously can't always be true after we implement `#[repr(align(N), pack(K))]`. There's also a case today where that assumption is not true, involving niche discriminants in enums:

Suppose that we have the code in rust-lang#53728:
```Rust
#[repr(u16)]
enum DeviceKind {
    Nil = 0,
}

#[repr(packed)]
struct DeviceInfo {
    endianness: u8,
    device_kind: DeviceKind,
}

struct Wrapper {
    device_info: DeviceInfo,
    data: u32
}
```

Observe the layout of `Option<Wrapper>`. It has an alignment of 4 because of the `u32`. `device_info.device_kind` is a good niche field to use, which means the enum ends up with this layout:
```
size = 8
align = 4
fields = [
    { offset=1, type=u16 } // discriminant, .<Some>.device_info.device_kind
]
```

And here we have an discriminant with alignment 2 (`u16`) but offset 1.
@bors
Copy link
Contributor

bors commented Sep 9, 2018

⌛ Testing commit b9e7574 with merge df6ba0c...

bors added a commit that referenced this pull request Sep 9, 2018
rustc_codegen_llvm: don't assume offsets are always aligned.

Fixes #53728 by taking into account not just overall type alignment and the field's alignment when determining whether a field is aligned or not ("packed"), but also the field's offset within the type.

Previously, rustc assumed that the offset was always at least as aligned as `min(struct.align, field.align)`. However, there's no real reason to have that assumption, and it obviously can't always be true after we implement `#[repr(align(N), pack(K))]`. There's also a case today where that assumption is not true, involving niche discriminants in enums:

Suppose that we have the code in #53728:
```Rust
#[repr(u16)]
enum DeviceKind {
    Nil = 0,
}

#[repr(packed)]
struct DeviceInfo {
    endianness: u8,
    device_kind: DeviceKind,
}

struct Wrapper {
    device_info: DeviceInfo,
    data: u32
}
```

Observe the layout of `Option<Wrapper>`. It has an alignment of 4 because of the `u32`. `device_info.device_kind` is a good niche field to use, which means the enum ends up with this layout:
```
size = 8
align = 4
fields = [
    { offset=1, type=u16 } // discriminant, .<Some>.device_info.device_kind
]
```

And here we have an discriminant with alignment 2 (`u16`) but offset 1.
@bors
Copy link
Contributor

bors commented Sep 9, 2018

☀️ Test successful - status-appveyor, status-travis
Approved by: oli-obk
Pushing df6ba0c to master...

@bors bors merged commit b9e7574 into rust-lang:master Sep 9, 2018
@eddyb eddyb deleted the issue-53728 branch September 9, 2018 14:55
@RalfJung
Copy link
Member

@eddyb so you mention on Discord that miri might also need a fix here. Do you have a testcase to check? I assume we'd currently throw an "unaligned access" error where we should not.

kennytm added a commit to kennytm/rust that referenced this pull request Sep 20, 2018
miri: correctly compute expected alignment for field

This is the miri version of rust-lang#53998. A test is added by rust-lang/miri#457.

r? @eddyb
bors added a commit to rust-lang/miri that referenced this pull request Jul 31, 2023
add some interesting tests for alignment corner cases

`strange_enum_discriminant_offset` example found in rust-lang/rust#53998.
bors added a commit to rust-lang/miri that referenced this pull request Jul 31, 2023
add some interesting tests for alignment corner cases

`strange_enum_discriminant_offset` example found in rust-lang/rust#53998.
RalfJung pushed a commit to RalfJung/rust that referenced this pull request Aug 6, 2023
add some interesting tests for alignment corner cases

`strange_enum_discriminant_offset` example found in rust-lang#53998.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants