Skip to content

Commit df6ba0c

Browse files
committed
Auto merge of #53998 - eddyb:issue-53728, r=oli-obk
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.
2 parents 3d2fc45 + b9e7574 commit df6ba0c

File tree

4 files changed

+63
-12
lines changed

4 files changed

+63
-12
lines changed

src/librustc_codegen_llvm/mir/place.rs

+6-3
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,10 @@ impl PlaceRef<'ll, 'tcx> {
173173
let cx = bx.cx;
174174
let field = self.layout.field(cx, ix);
175175
let offset = self.layout.fields.offset(ix);
176-
let align = self.align.min(self.layout.align).min(field.align);
176+
let effective_field_align = self.align
177+
.min(self.layout.align)
178+
.min(field.align)
179+
.restrict_for_offset(offset);
177180

178181
let simple = || {
179182
// Unions and newtypes only use an offset of 0.
@@ -195,7 +198,7 @@ impl PlaceRef<'ll, 'tcx> {
195198
None
196199
},
197200
layout: field,
198-
align,
201+
align: effective_field_align,
199202
}
200203
};
201204

@@ -268,7 +271,7 @@ impl PlaceRef<'ll, 'tcx> {
268271
llval: bx.pointercast(byte_ptr, ll_fty.ptr_to()),
269272
llextra: self.llextra,
270273
layout: field,
271-
align,
274+
align: effective_field_align,
272275
}
273276
}
274277

src/librustc_codegen_llvm/type_of.rs

+13-9
Original file line numberDiff line numberDiff line change
@@ -122,33 +122,37 @@ fn struct_llfields<'a, 'tcx>(cx: &CodegenCx<'a, 'tcx>,
122122

123123
let mut packed = false;
124124
let mut offset = Size::ZERO;
125-
let mut prev_align = layout.align;
125+
let mut prev_effective_align = layout.align;
126126
let mut result: Vec<_> = Vec::with_capacity(1 + field_count * 2);
127127
for i in layout.fields.index_by_increasing_offset() {
128-
let field = layout.field(cx, i);
129-
packed |= layout.align.abi() < field.align.abi();
130-
131128
let target_offset = layout.fields.offset(i as usize);
132-
debug!("struct_llfields: {}: {:?} offset: {:?} target_offset: {:?}",
133-
i, field, offset, target_offset);
129+
let field = layout.field(cx, i);
130+
let effective_field_align = layout.align
131+
.min(field.align)
132+
.restrict_for_offset(target_offset);
133+
packed |= effective_field_align.abi() < field.align.abi();
134+
135+
debug!("struct_llfields: {}: {:?} offset: {:?} target_offset: {:?} \
136+
effective_field_align: {}",
137+
i, field, offset, target_offset, effective_field_align.abi());
134138
assert!(target_offset >= offset);
135139
let padding = target_offset - offset;
136-
let padding_align = layout.align.min(prev_align).min(field.align);
140+
let padding_align = prev_effective_align.min(effective_field_align);
137141
assert_eq!(offset.abi_align(padding_align) + padding, target_offset);
138142
result.push(Type::padding_filler(cx, padding, padding_align));
139143
debug!(" padding before: {:?}", padding);
140144

141145
result.push(field.llvm_type(cx));
142146
offset = target_offset + field.size;
143-
prev_align = field.align;
147+
prev_effective_align = effective_field_align;
144148
}
145149
if !layout.is_unsized() && field_count > 0 {
146150
if offset > layout.size {
147151
bug!("layout: {:#?} stride: {:?} offset: {:?}",
148152
layout, layout.size, offset);
149153
}
150154
let padding = layout.size - offset;
151-
let padding_align = layout.align.min(prev_align);
155+
let padding_align = prev_effective_align;
152156
assert_eq!(offset.abi_align(padding_align) + padding, layout.size);
153157
debug!("struct_llfields: pad_bytes: {:?} offset: {:?} stride: {:?}",
154158
padding, offset, layout.size);

src/librustc_target/abi/mod.rs

+18
Original file line numberDiff line numberDiff line change
@@ -416,6 +416,24 @@ impl Align {
416416
pref_pow2: cmp::max(self.pref_pow2, other.pref_pow2),
417417
}
418418
}
419+
420+
/// Compute the best alignment possible for the given offset
421+
/// (the largest power of two that the offset is a multiple of).
422+
///
423+
/// NB: for an offset of `0`, this happens to return `2^64`.
424+
pub fn max_for_offset(offset: Size) -> Align {
425+
let pow2 = offset.bytes().trailing_zeros() as u8;
426+
Align {
427+
abi_pow2: pow2,
428+
pref_pow2: pow2,
429+
}
430+
}
431+
432+
/// Lower the alignment, if necessary, such that the given offset
433+
/// is aligned to it (the offset is a multiple of the aligment).
434+
pub fn restrict_for_offset(self, offset: Size) -> Align {
435+
self.min(Align::max_for_offset(offset))
436+
}
419437
}
420438

421439
/// Integers, also used for enum discriminants.

src/test/run-pass/issue-53728.rs

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// Copyright 2018 The Rust Project Developers. See the COPYRIGHT
2+
// file at the top-level directory of this distribution and at
3+
// http://rust-lang.org/COPYRIGHT.
4+
//
5+
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6+
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7+
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8+
// option. This file may not be copied, modified, or distributed
9+
// except according to those terms.
10+
11+
#[repr(u16)]
12+
enum DeviceKind {
13+
Nil = 0,
14+
}
15+
16+
#[repr(packed)]
17+
struct DeviceInfo {
18+
endianness: u8,
19+
device_kind: DeviceKind,
20+
}
21+
22+
fn main() {
23+
let _x = None::<(DeviceInfo, u8)>;
24+
let _y = None::<(DeviceInfo, u16)>;
25+
let _z = None::<(DeviceInfo, u64)>;
26+
}

0 commit comments

Comments
 (0)