Skip to content

Commit 151a070

Browse files
committed
Auto merge of #104872 - luqmana:packed-union-align, r=oli-obk
Avoid alignment mismatch between ABI and layout for unions. Fixes #104802 Fixes #103634 r? `@eddyb` cc `@RalfJung`
2 parents 6e96802 + 8e7714d commit 151a070

File tree

6 files changed

+397
-81
lines changed

6 files changed

+397
-81
lines changed

compiler/rustc_abi/src/layout.rs

+51-20
Original file line numberDiff line numberDiff line change
@@ -729,42 +729,73 @@ pub trait LayoutCalculator {
729729
align = align.max(AbiAndPrefAlign::new(repr_align));
730730
}
731731

732-
let optimize = !repr.inhibit_union_abi_opt();
732+
// If all the non-ZST fields have the same ABI and union ABI optimizations aren't
733+
// disabled, we can use that common ABI for the union as a whole.
734+
struct AbiMismatch;
735+
let mut common_non_zst_abi_and_align = if repr.inhibit_union_abi_opt() {
736+
// Can't optimize
737+
Err(AbiMismatch)
738+
} else {
739+
Ok(None)
740+
};
741+
733742
let mut size = Size::ZERO;
734-
let mut abi = Abi::Aggregate { sized: true };
735743
let only_variant = &variants[FIRST_VARIANT];
736744
for field in only_variant {
737745
assert!(field.0.is_sized());
746+
738747
align = align.max(field.align());
748+
size = cmp::max(size, field.size());
739749

740-
// If all non-ZST fields have the same ABI, forward this ABI
741-
if optimize && !field.0.is_zst() {
750+
if field.0.is_zst() {
751+
// Nothing more to do for ZST fields
752+
continue;
753+
}
754+
755+
if let Ok(common) = common_non_zst_abi_and_align {
742756
// Discard valid range information and allow undef
743-
let field_abi = match field.abi() {
744-
Abi::Scalar(x) => Abi::Scalar(x.to_union()),
745-
Abi::ScalarPair(x, y) => Abi::ScalarPair(x.to_union(), y.to_union()),
746-
Abi::Vector { element: x, count } => {
747-
Abi::Vector { element: x.to_union(), count }
748-
}
749-
Abi::Uninhabited | Abi::Aggregate { .. } => Abi::Aggregate { sized: true },
750-
};
757+
let field_abi = field.abi().to_union();
751758

752-
if size == Size::ZERO {
753-
// first non ZST: initialize 'abi'
754-
abi = field_abi;
755-
} else if abi != field_abi {
756-
// different fields have different ABI: reset to Aggregate
757-
abi = Abi::Aggregate { sized: true };
759+
if let Some((common_abi, common_align)) = common {
760+
if common_abi != field_abi {
761+
// Different fields have different ABI: disable opt
762+
common_non_zst_abi_and_align = Err(AbiMismatch);
763+
} else {
764+
// Fields with the same non-Aggregate ABI should also
765+
// have the same alignment
766+
if !matches!(common_abi, Abi::Aggregate { .. }) {
767+
assert_eq!(
768+
common_align,
769+
field.align().abi,
770+
"non-Aggregate field with matching ABI but differing alignment"
771+
);
772+
}
773+
}
774+
} else {
775+
// First non-ZST field: record its ABI and alignment
776+
common_non_zst_abi_and_align = Ok(Some((field_abi, field.align().abi)));
758777
}
759778
}
760-
761-
size = cmp::max(size, field.size());
762779
}
763780

764781
if let Some(pack) = repr.pack {
765782
align = align.min(AbiAndPrefAlign::new(pack));
766783
}
767784

785+
// If all non-ZST fields have the same ABI, we may forward that ABI
786+
// for the union as a whole, unless otherwise inhibited.
787+
let abi = match common_non_zst_abi_and_align {
788+
Err(AbiMismatch) | Ok(None) => Abi::Aggregate { sized: true },
789+
Ok(Some((abi, _))) => {
790+
if abi.inherent_align(dl).map(|a| a.abi) != Some(align.abi) {
791+
// Mismatched alignment (e.g. union is #[repr(packed)]): disable opt
792+
Abi::Aggregate { sized: true }
793+
} else {
794+
abi
795+
}
796+
}
797+
};
798+
768799
Some(LayoutS {
769800
variants: Variants::Single { index: FIRST_VARIANT },
770801
fields: FieldsShape::Union(NonZeroUsize::new(only_variant.len())?),

compiler/rustc_abi/src/lib.rs

+44
Original file line numberDiff line numberDiff line change
@@ -1272,6 +1272,50 @@ impl Abi {
12721272
pub fn is_scalar(&self) -> bool {
12731273
matches!(*self, Abi::Scalar(_))
12741274
}
1275+
1276+
/// Returns the fixed alignment of this ABI, if any is mandated.
1277+
pub fn inherent_align<C: HasDataLayout>(&self, cx: &C) -> Option<AbiAndPrefAlign> {
1278+
Some(match *self {
1279+
Abi::Scalar(s) => s.align(cx),
1280+
Abi::ScalarPair(s1, s2) => s1.align(cx).max(s2.align(cx)),
1281+
Abi::Vector { element, count } => {
1282+
cx.data_layout().vector_align(element.size(cx) * count)
1283+
}
1284+
Abi::Uninhabited | Abi::Aggregate { .. } => return None,
1285+
})
1286+
}
1287+
1288+
/// Returns the fixed size of this ABI, if any is mandated.
1289+
pub fn inherent_size<C: HasDataLayout>(&self, cx: &C) -> Option<Size> {
1290+
Some(match *self {
1291+
Abi::Scalar(s) => {
1292+
// No padding in scalars.
1293+
s.size(cx)
1294+
}
1295+
Abi::ScalarPair(s1, s2) => {
1296+
// May have some padding between the pair.
1297+
let field2_offset = s1.size(cx).align_to(s2.align(cx).abi);
1298+
(field2_offset + s2.size(cx)).align_to(self.inherent_align(cx)?.abi)
1299+
}
1300+
Abi::Vector { element, count } => {
1301+
// No padding in vectors, except possibly for trailing padding
1302+
// to make the size a multiple of align (e.g. for vectors of size 3).
1303+
(element.size(cx) * count).align_to(self.inherent_align(cx)?.abi)
1304+
}
1305+
Abi::Uninhabited | Abi::Aggregate { .. } => return None,
1306+
})
1307+
}
1308+
1309+
/// Discard validity range information and allow undef.
1310+
pub fn to_union(&self) -> Self {
1311+
assert!(self.is_sized());
1312+
match *self {
1313+
Abi::Scalar(s) => Abi::Scalar(s.to_union()),
1314+
Abi::ScalarPair(s1, s2) => Abi::ScalarPair(s1.to_union(), s2.to_union()),
1315+
Abi::Vector { element, count } => Abi::Vector { element: element.to_union(), count },
1316+
Abi::Uninhabited | Abi::Aggregate { .. } => Abi::Aggregate { sized: true },
1317+
}
1318+
}
12751319
}
12761320

12771321
#[derive(PartialEq, Eq, Hash, Clone, Debug)]

compiler/rustc_ty_utils/src/layout_sanity_check.rs

+40-54
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use rustc_middle::ty::{
44
};
55
use rustc_target::abi::*;
66

7-
use std::cmp;
7+
use std::assert_matches::assert_matches;
88

99
/// Enforce some basic invariants on layouts.
1010
pub(super) fn sanity_check_layout<'tcx>(
@@ -68,21 +68,31 @@ pub(super) fn sanity_check_layout<'tcx>(
6868
}
6969

7070
fn check_layout_abi<'tcx>(cx: &LayoutCx<'tcx, TyCtxt<'tcx>>, layout: &TyAndLayout<'tcx>) {
71+
// Verify the ABI mandated alignment and size.
72+
let align = layout.abi.inherent_align(cx).map(|align| align.abi);
73+
let size = layout.abi.inherent_size(cx);
74+
let Some((align, size)) = align.zip(size) else {
75+
assert_matches!(
76+
layout.layout.abi(),
77+
Abi::Uninhabited | Abi::Aggregate { .. },
78+
"ABI unexpectedly missing alignment and/or size in {layout:#?}"
79+
);
80+
return
81+
};
82+
assert_eq!(
83+
layout.layout.align().abi,
84+
align,
85+
"alignment mismatch between ABI and layout in {layout:#?}"
86+
);
87+
assert_eq!(
88+
layout.layout.size(),
89+
size,
90+
"size mismatch between ABI and layout in {layout:#?}"
91+
);
92+
93+
// Verify per-ABI invariants
7194
match layout.layout.abi() {
72-
Abi::Scalar(scalar) => {
73-
// No padding in scalars.
74-
let size = scalar.size(cx);
75-
let align = scalar.align(cx).abi;
76-
assert_eq!(
77-
layout.layout.size(),
78-
size,
79-
"size mismatch between ABI and layout in {layout:#?}"
80-
);
81-
assert_eq!(
82-
layout.layout.align().abi,
83-
align,
84-
"alignment mismatch between ABI and layout in {layout:#?}"
85-
);
95+
Abi::Scalar(_) => {
8696
// Check that this matches the underlying field.
8797
let inner = skip_newtypes(cx, layout);
8898
assert!(
@@ -135,24 +145,6 @@ pub(super) fn sanity_check_layout<'tcx>(
135145
}
136146
}
137147
Abi::ScalarPair(scalar1, scalar2) => {
138-
// Sanity-check scalar pairs. Computing the expected size and alignment is a bit of work.
139-
let size1 = scalar1.size(cx);
140-
let align1 = scalar1.align(cx).abi;
141-
let size2 = scalar2.size(cx);
142-
let align2 = scalar2.align(cx).abi;
143-
let align = cmp::max(align1, align2);
144-
let field2_offset = size1.align_to(align2);
145-
let size = (field2_offset + size2).align_to(align);
146-
assert_eq!(
147-
layout.layout.size(),
148-
size,
149-
"size mismatch between ABI and layout in {layout:#?}"
150-
);
151-
assert_eq!(
152-
layout.layout.align().abi,
153-
align,
154-
"alignment mismatch between ABI and layout in {layout:#?}",
155-
);
156148
// Check that the underlying pair of fields matches.
157149
let inner = skip_newtypes(cx, layout);
158150
assert!(
@@ -189,8 +181,9 @@ pub(super) fn sanity_check_layout<'tcx>(
189181
"`ScalarPair` layout for type with less than two non-ZST fields: {inner:#?}"
190182
)
191183
});
192-
assert!(
193-
fields.next().is_none(),
184+
assert_matches!(
185+
fields.next(),
186+
None,
194187
"`ScalarPair` layout for type with at least three non-ZST fields: {inner:#?}"
195188
);
196189
// The fields might be in opposite order.
@@ -200,6 +193,10 @@ pub(super) fn sanity_check_layout<'tcx>(
200193
(offset2, field2, offset1, field1)
201194
};
202195
// The fields should be at the right offset, and match the `scalar` layout.
196+
let size1 = scalar1.size(cx);
197+
let align1 = scalar1.align(cx).abi;
198+
let size2 = scalar2.size(cx);
199+
let align2 = scalar2.align(cx).abi;
203200
assert_eq!(
204201
offset1,
205202
Size::ZERO,
@@ -213,10 +210,12 @@ pub(super) fn sanity_check_layout<'tcx>(
213210
field1.align.abi, align1,
214211
"`ScalarPair` first field with bad align in {inner:#?}",
215212
);
216-
assert!(
217-
matches!(field1.abi, Abi::Scalar(_)),
213+
assert_matches!(
214+
field1.abi,
215+
Abi::Scalar(_),
218216
"`ScalarPair` first field with bad ABI in {inner:#?}",
219217
);
218+
let field2_offset = size1.align_to(align2);
220219
assert_eq!(
221220
offset2, field2_offset,
222221
"`ScalarPair` second field at bad offset in {inner:#?}",
@@ -229,27 +228,14 @@ pub(super) fn sanity_check_layout<'tcx>(
229228
field2.align.abi, align2,
230229
"`ScalarPair` second field with bad align in {inner:#?}",
231230
);
232-
assert!(
233-
matches!(field2.abi, Abi::Scalar(_)),
231+
assert_matches!(
232+
field2.abi,
233+
Abi::Scalar(_),
234234
"`ScalarPair` second field with bad ABI in {inner:#?}",
235235
);
236236
}
237-
Abi::Vector { count, element } => {
238-
// No padding in vectors, except possibly for trailing padding to make the size a multiple of align.
239-
let size = element.size(cx) * count;
240-
let align = cx.data_layout().vector_align(size).abi;
241-
let size = size.align_to(align); // needed e.g. for vectors of size 3
237+
Abi::Vector { element, .. } => {
242238
assert!(align >= element.align(cx).abi); // just sanity-checking `vector_align`.
243-
assert_eq!(
244-
layout.layout.size(),
245-
size,
246-
"size mismatch between ABI and layout in {layout:#?}"
247-
);
248-
assert_eq!(
249-
layout.layout.align().abi,
250-
align,
251-
"alignment mismatch between ABI and layout in {layout:#?}"
252-
);
253239
// FIXME: Do some kind of check of the inner type, like for Scalar and ScalarPair.
254240
}
255241
Abi::Uninhabited | Abi::Aggregate { .. } => {} // Nothing to check.

compiler/rustc_ty_utils/src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
//! This API is completely unstable and subject to change.
66
77
#![doc(html_root_url = "https://doc.rust-lang.org/nightly/nightly-rustc/")]
8+
#![feature(assert_matches)]
89
#![feature(iterator_try_collect)]
910
#![feature(let_chains)]
1011
#![feature(never_type)]

tests/ui/layout/debug.rs

+47-1
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
// normalize-stderr-test "pref: Align\([1-8] bytes\)" -> "pref: $$PREF_ALIGN"
2-
#![feature(never_type, rustc_attrs, type_alias_impl_trait)]
2+
#![feature(never_type, rustc_attrs, type_alias_impl_trait, repr_simd)]
33
#![crate_type = "lib"]
44

55
#[rustc_layout(debug)]
6+
#[derive(Copy, Clone)]
67
enum E { Foo, Bar(!, i32, i32) } //~ ERROR: layout_of
78

89
#[rustc_layout(debug)]
@@ -17,6 +18,51 @@ type Test = Result<i32, i32>; //~ ERROR: layout_of
1718
#[rustc_layout(debug)]
1819
type T = impl std::fmt::Debug; //~ ERROR: layout_of
1920

21+
#[rustc_layout(debug)]
22+
pub union V { //~ ERROR: layout_of
23+
a: [u16; 0],
24+
b: u8,
25+
}
26+
27+
#[rustc_layout(debug)]
28+
pub union W { //~ ERROR: layout_of
29+
b: u8,
30+
a: [u16; 0],
31+
}
32+
33+
#[rustc_layout(debug)]
34+
pub union Y { //~ ERROR: layout_of
35+
b: [u8; 0],
36+
a: [u16; 0],
37+
}
38+
39+
#[rustc_layout(debug)]
40+
#[repr(packed(1))]
41+
union P1 { x: u32 } //~ ERROR: layout_of
42+
43+
#[rustc_layout(debug)]
44+
#[repr(packed(1))]
45+
union P2 { x: (u32, u32) } //~ ERROR: layout_of
46+
47+
#[repr(simd)]
48+
#[derive(Copy, Clone)]
49+
struct F32x4(f32, f32, f32, f32);
50+
51+
#[rustc_layout(debug)]
52+
#[repr(packed(1))]
53+
union P3 { x: F32x4 } //~ ERROR: layout_of
54+
55+
#[rustc_layout(debug)]
56+
#[repr(packed(1))]
57+
union P4 { x: E } //~ ERROR: layout_of
58+
59+
#[rustc_layout(debug)]
60+
#[repr(packed(1))]
61+
union P5 { zst: [u16; 0], byte: u8 } //~ ERROR: layout_of
62+
63+
#[rustc_layout(debug)]
64+
type X = std::mem::MaybeUninit<u8>; //~ ERROR: layout_of
65+
2066
fn f() -> T {
2167
0i32
2268
}

0 commit comments

Comments
 (0)