Skip to content

Commit 0b84f18

Browse files
committed
Auto merge of #115277 - RalfJung:is_1zst, r=davidtwco
fix some issues around ZST handling This fixes two bugs: - We used to entirely skip enum variants like `B([u16; 0], !)`, even failing to properly align the enum! Honoring the alignment of uninhabited variants is important for the same reason that we must reserve space for their fields -- see [here](#49298 (comment)) for why. - ~~We uses to reject `repr(transparent)` on `struct MyType([u16; 0])`, which is weird because a one-field struct should always be allowed to be transparent around that field.~~ (moved to separate PR) I also found two places in the layout code that did something special for ZST without explaining why, and removing those special cases doesn't seem to have any effect except for reordering some zero-sized fields which shouldn't be an issue... maybe PR CI will explain why those cases were needed, or maybe they became obsolete at some point.
2 parents f6faef4 + b2ebf1c commit 0b84f18

File tree

20 files changed

+130
-48
lines changed

20 files changed

+130
-48
lines changed

compiler/rustc_abi/src/layout.rs

+13-12
Original file line numberDiff line numberDiff line change
@@ -157,8 +157,10 @@ pub trait LayoutCalculator {
157157
// for non-ZST uninhabited data (mostly partial initialization).
158158
let absent = |fields: &IndexSlice<FieldIdx, Layout<'_>>| {
159159
let uninhabited = fields.iter().any(|f| f.abi().is_uninhabited());
160-
let is_zst = fields.iter().all(|f| f.0.is_zst());
161-
uninhabited && is_zst
160+
// We cannot ignore alignment; that might lead us to entirely discard a variant and
161+
// produce an enum that is less aligned than it should be!
162+
let is_1zst = fields.iter().all(|f| f.0.is_1zst());
163+
uninhabited && is_1zst
162164
};
163165
let (present_first, present_second) = {
164166
let mut present_variants = variants
@@ -357,10 +359,8 @@ pub trait LayoutCalculator {
357359
// It'll fit, but we need to make some adjustments.
358360
match layout.fields {
359361
FieldsShape::Arbitrary { ref mut offsets, .. } => {
360-
for (j, offset) in offsets.iter_enumerated_mut() {
361-
if !variants[i][j].0.is_zst() {
362-
*offset += this_offset;
363-
}
362+
for offset in offsets.iter_mut() {
363+
*offset += this_offset;
364364
}
365365
}
366366
_ => {
@@ -504,7 +504,7 @@ pub trait LayoutCalculator {
504504
// to make room for a larger discriminant.
505505
for field_idx in st.fields.index_by_increasing_offset() {
506506
let field = &field_layouts[FieldIdx::from_usize(field_idx)];
507-
if !field.0.is_zst() || field.align().abi.bytes() != 1 {
507+
if !field.0.is_1zst() {
508508
start_align = start_align.min(field.align().abi);
509509
break;
510510
}
@@ -603,12 +603,15 @@ pub trait LayoutCalculator {
603603
abi = Abi::Scalar(tag);
604604
} else {
605605
// Try to use a ScalarPair for all tagged enums.
606+
// That's possible only if we can find a common primitive type for all variants.
606607
let mut common_prim = None;
607608
let mut common_prim_initialized_in_all_variants = true;
608609
for (field_layouts, layout_variant) in iter::zip(variants, &layout_variants) {
609610
let FieldsShape::Arbitrary { ref offsets, .. } = layout_variant.fields else {
610611
panic!();
611612
};
613+
// We skip *all* ZST here and later check if we are good in terms of alignment.
614+
// This lets us handle some cases involving aligned ZST.
612615
let mut fields = iter::zip(field_layouts, offsets).filter(|p| !p.0.0.is_zst());
613616
let (field, offset) = match (fields.next(), fields.next()) {
614617
(None, None) => {
@@ -954,9 +957,6 @@ fn univariant(
954957
};
955958

956959
(
957-
// Place ZSTs first to avoid "interesting offsets", especially with only one
958-
// or two non-ZST fields. This helps Scalar/ScalarPair layouts.
959-
!f.0.is_zst(),
960960
// Then place largest alignments first.
961961
cmp::Reverse(alignment_group_key(f)),
962962
// Then prioritize niche placement within alignment group according to
@@ -1073,9 +1073,10 @@ fn univariant(
10731073
let size = min_size.align_to(align.abi);
10741074
let mut layout_of_single_non_zst_field = None;
10751075
let mut abi = Abi::Aggregate { sized };
1076-
// Unpack newtype ABIs and find scalar pairs.
1076+
// Try to make this a Scalar/ScalarPair.
10771077
if sized && size.bytes() > 0 {
1078-
// All other fields must be ZSTs.
1078+
// We skip *all* ZST here and later check if we are good in terms of alignment.
1079+
// This lets us handle some cases involving aligned ZST.
10791080
let mut non_zst_fields = fields.iter_enumerated().filter(|&(_, f)| !f.0.is_zst());
10801081

10811082
match (non_zst_fields.next(), non_zst_fields.next(), non_zst_fields.next()) {

compiler/rustc_abi/src/lib.rs

+10
Original file line numberDiff line numberDiff line change
@@ -1660,15 +1660,25 @@ pub struct PointeeInfo {
16601660

16611661
impl LayoutS {
16621662
/// Returns `true` if the layout corresponds to an unsized type.
1663+
#[inline]
16631664
pub fn is_unsized(&self) -> bool {
16641665
self.abi.is_unsized()
16651666
}
16661667

1668+
#[inline]
16671669
pub fn is_sized(&self) -> bool {
16681670
self.abi.is_sized()
16691671
}
16701672

1673+
/// Returns `true` if the type is sized and a 1-ZST (meaning it has size 0 and alignment 1).
1674+
pub fn is_1zst(&self) -> bool {
1675+
self.is_sized() && self.size.bytes() == 0 && self.align.abi.bytes() == 1
1676+
}
1677+
16711678
/// Returns `true` if the type is a ZST and not unsized.
1679+
///
1680+
/// Note that this does *not* imply that the type is irrelevant for layout! It can still have
1681+
/// non-trivial alignment constraints. You probably want to use `is_1zst` instead.
16721682
pub fn is_zst(&self) -> bool {
16731683
match self.abi {
16741684
Abi::Scalar(_) | Abi::ScalarPair(..) | Abi::Vector { .. } => false,

compiler/rustc_codegen_cranelift/src/unsize.rs

+3-1
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,8 @@ fn unsize_ptr<'tcx>(
8888
let src_f = src_layout.field(fx, i);
8989
assert_eq!(src_layout.fields.offset(i).bytes(), 0);
9090
assert_eq!(dst_layout.fields.offset(i).bytes(), 0);
91-
if src_f.is_zst() {
91+
if src_f.is_1zst() {
92+
// We are looking for the one non-1-ZST field; this is not it.
9293
continue;
9394
}
9495
assert_eq!(src_layout.size, src_f.size);
@@ -151,6 +152,7 @@ pub(crate) fn coerce_unsized_into<'tcx>(
151152
let dst_f = dst.place_field(fx, FieldIdx::new(i));
152153

153154
if dst_f.layout().is_zst() {
155+
// No data here, nothing to copy/coerce.
154156
continue;
155157
}
156158

compiler/rustc_codegen_cranelift/src/vtable.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,8 @@ pub(crate) fn get_ptr_and_method_ref<'tcx>(
5151
'descend_newtypes: while !arg.layout().ty.is_unsafe_ptr() && !arg.layout().ty.is_ref() {
5252
for i in 0..arg.layout().fields.count() {
5353
let field = arg.value_field(fx, FieldIdx::new(i));
54-
if !field.layout().is_zst() {
55-
// we found the one non-zero-sized field that is allowed
54+
if !field.layout().is_1zst() {
55+
// we found the one non-1-ZST field that is allowed
5656
// now find *its* non-zero-sized field, or stop if it's a
5757
// pointer
5858
arg = field;

compiler/rustc_codegen_llvm/src/debuginfo/metadata.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -445,9 +445,9 @@ pub fn type_di_node<'ll, 'tcx>(cx: &CodegenCx<'ll, 'tcx>, t: Ty<'tcx>) -> &'ll D
445445
ty::RawPtr(ty::TypeAndMut { ty: pointee_type, .. }) | ty::Ref(_, pointee_type, _) => {
446446
build_pointer_or_reference_di_node(cx, t, pointee_type, unique_type_id)
447447
}
448-
// Box<T, A> may have a non-ZST allocator A. In that case, we
448+
// Box<T, A> may have a non-1-ZST allocator A. In that case, we
449449
// cannot treat Box<T, A> as just an owned alias of `*mut T`.
450-
ty::Adt(def, args) if def.is_box() && cx.layout_of(args.type_at(1)).is_zst() => {
450+
ty::Adt(def, args) if def.is_box() && cx.layout_of(args.type_at(1)).is_1zst() => {
451451
build_pointer_or_reference_di_node(cx, t, t.boxed_ty(), unique_type_id)
452452
}
453453
ty::FnDef(..) | ty::FnPtr(_) => build_subroutine_type_di_node(cx, unique_type_id),

compiler/rustc_codegen_ssa/src/base.rs

+5-3
Original file line numberDiff line numberDiff line change
@@ -202,7 +202,7 @@ pub fn unsize_ptr<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>(
202202
(src, unsized_info(bx, a, b, old_info))
203203
}
204204
(&ty::Adt(def_a, _), &ty::Adt(def_b, _)) => {
205-
assert_eq!(def_a, def_b);
205+
assert_eq!(def_a, def_b); // implies same number of fields
206206
let src_layout = bx.cx().layout_of(src_ty);
207207
let dst_layout = bx.cx().layout_of(dst_ty);
208208
if src_ty == dst_ty {
@@ -211,7 +211,8 @@ pub fn unsize_ptr<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>(
211211
let mut result = None;
212212
for i in 0..src_layout.fields.count() {
213213
let src_f = src_layout.field(bx.cx(), i);
214-
if src_f.is_zst() {
214+
if src_f.is_1zst() {
215+
// We are looking for the one non-1-ZST field; this is not it.
215216
continue;
216217
}
217218

@@ -272,13 +273,14 @@ pub fn coerce_unsized_into<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>(
272273
}
273274

274275
(&ty::Adt(def_a, _), &ty::Adt(def_b, _)) => {
275-
assert_eq!(def_a, def_b);
276+
assert_eq!(def_a, def_b); // implies same number of fields
276277

277278
for i in def_a.variant(FIRST_VARIANT).fields.indices() {
278279
let src_f = src.project_field(bx, i.as_usize());
279280
let dst_f = dst.project_field(bx, i.as_usize());
280281

281282
if dst_f.layout.is_zst() {
283+
// No data here, nothing to copy/coerce.
282284
continue;
283285
}
284286

compiler/rustc_codegen_ssa/src/mir/block.rs

+7-6
Original file line numberDiff line numberDiff line change
@@ -933,8 +933,8 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
933933
{
934934
for i in 0..op.layout.fields.count() {
935935
let field = op.extract_field(bx, i);
936-
if !field.layout.is_zst() {
937-
// we found the one non-zero-sized field that is allowed
936+
if !field.layout.is_1zst() {
937+
// we found the one non-1-ZST field that is allowed
938938
// now find *its* non-zero-sized field, or stop if it's a
939939
// pointer
940940
op = field;
@@ -975,10 +975,11 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
975975
{
976976
for i in 0..op.layout.fields.count() {
977977
let field = op.extract_field(bx, i);
978-
if !field.layout.is_zst() {
979-
// we found the one non-zero-sized field that is allowed
980-
// now find *its* non-zero-sized field, or stop if it's a
981-
// pointer
978+
if !field.layout.is_1zst() {
979+
// We found the one non-1-ZST field that is allowed. (The rules
980+
// for `DispatchFromDyn` ensure there's exactly one such field.)
981+
// Now find *its* non-zero-sized field, or stop if it's a
982+
// pointer.
982983
op = field;
983984
continue 'descend_newtypes;
984985
}

compiler/rustc_codegen_ssa/src/mir/mod.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,7 @@ impl<'tcx, V: CodegenObject> LocalRef<'tcx, V> {
145145
if layout.is_zst() {
146146
// Zero-size temporaries aren't always initialized, which
147147
// doesn't matter because they don't contain data, but
148-
// we need something in the operand.
148+
// we need something sufficiently aligned in the operand.
149149
LocalRef::Operand(OperandRef::zero_sized(layout))
150150
} else {
151151
LocalRef::PendingOperand

compiler/rustc_codegen_ssa/src/mir/operand.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,8 @@ pub enum OperandValue<V> {
5050
/// from [`ConstMethods::const_poison`].
5151
///
5252
/// An `OperandValue` *must* be this variant for any type for which
53-
/// `is_zst` on its `Layout` returns `true`.
53+
/// `is_zst` on its `Layout` returns `true`. Note however that
54+
/// these values can still require alignment.
5455
ZeroSized,
5556
}
5657

compiler/rustc_codegen_ssa/src/mir/place.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,8 @@ impl<'a, 'tcx, V: CodegenObject> PlaceRef<'tcx, V> {
114114
bx.struct_gep(ty, self.llval, 1)
115115
}
116116
Abi::Scalar(_) | Abi::ScalarPair(..) | Abi::Vector { .. } if field.is_zst() => {
117-
// ZST fields are not included in Scalar, ScalarPair, and Vector layouts, so manually offset the pointer.
117+
// ZST fields (even some that require alignment) are not included in Scalar,
118+
// ScalarPair, and Vector layouts, so manually offset the pointer.
118119
bx.gep(bx.cx().type_i8(), self.llval, &[bx.const_usize(offset.bytes())])
119120
}
120121
Abi::Scalar(_) | Abi::ScalarPair(..) => {

compiler/rustc_codegen_ssa/src/mir/rvalue.rs

+1
Original file line numberDiff line numberDiff line change
@@ -1004,6 +1004,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
10041004
mir::Rvalue::Aggregate(..) => {
10051005
let ty = rvalue.ty(self.mir, self.cx.tcx());
10061006
let ty = self.monomorphize(ty);
1007+
// For ZST this can be `OperandValueKind::ZeroSized`.
10071008
self.cx.spanned_layout_of(ty, span).is_zst()
10081009
}
10091010
}

compiler/rustc_const_eval/src/interpret/cast.rs

+12-8
Original file line numberDiff line numberDiff line change
@@ -410,21 +410,25 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
410410
self.unsize_into_ptr(src, dest, *s, *c)
411411
}
412412
(&ty::Adt(def_a, _), &ty::Adt(def_b, _)) => {
413-
assert_eq!(def_a, def_b);
413+
assert_eq!(def_a, def_b); // implies same number of fields
414414

415-
// unsizing of generic struct with pointer fields
416-
// Example: `Arc<T>` -> `Arc<Trait>`
417-
// here we need to increase the size of every &T thin ptr field to a fat ptr
415+
// Unsizing of generic struct with pointer fields, like `Arc<T>` -> `Arc<Trait>`.
416+
// There can be extra fields as long as they don't change their type or are 1-ZST.
417+
// There might also be no field that actually needs unsizing.
418+
let mut found_cast_field = false;
418419
for i in 0..src.layout.fields.count() {
419420
let cast_ty_field = cast_ty.field(self, i);
420-
if cast_ty_field.is_zst() {
421-
continue;
422-
}
423421
let src_field = self.project_field(src, i)?;
424422
let dst_field = self.project_field(dest, i)?;
425-
if src_field.layout.ty == cast_ty_field.ty {
423+
if src_field.layout.is_1zst() && cast_ty_field.is_1zst() {
424+
// Skip 1-ZST fields.
425+
} else if src_field.layout.ty == cast_ty_field.ty {
426426
self.copy_op(&src_field, &dst_field, /*allow_transmute*/ false)?;
427427
} else {
428+
if found_cast_field {
429+
span_bug!(self.cur_span(), "unsize_into: more than one field to cast");
430+
}
431+
found_cast_field = true;
428432
self.unsize_into(&src_field, cast_ty_field, &dst_field)?;
429433
}
430434
}

compiler/rustc_const_eval/src/interpret/operand.rs

+1
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,7 @@ impl<'tcx, Prov: Provenance> ImmTy<'tcx, Prov> {
239239
// if the entire value is uninit, then so is the field (can happen in ConstProp)
240240
(Immediate::Uninit, _) => Immediate::Uninit,
241241
// the field contains no information, can be left uninit
242+
// (Scalar/ScalarPair can contain even aligned ZST, not just 1-ZST)
242243
_ if layout.is_zst() => Immediate::Uninit,
243244
// some fieldless enum variants can have non-zero size but still `Aggregate` ABI... try
244245
// to detect those here and also give them no data

compiler/rustc_const_eval/src/interpret/terminator.rs

+4-4
Original file line numberDiff line numberDiff line change
@@ -684,23 +684,23 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
684684
}
685685
_ => {
686686
// Not there yet, search for the only non-ZST field.
687+
// (The rules for `DispatchFromDyn` ensure there's exactly one such field.)
687688
let mut non_zst_field = None;
688689
for i in 0..receiver.layout.fields.count() {
689690
let field = self.project_field(&receiver, i)?;
690-
let zst =
691-
field.layout.is_zst() && field.layout.align.abi.bytes() == 1;
691+
let zst = field.layout.is_1zst();
692692
if !zst {
693693
assert!(
694694
non_zst_field.is_none(),
695-
"multiple non-ZST fields in dyn receiver type {}",
695+
"multiple non-1-ZST fields in dyn receiver type {}",
696696
receiver.layout.ty
697697
);
698698
non_zst_field = Some(field);
699699
}
700700
}
701701
receiver = non_zst_field.unwrap_or_else(|| {
702702
panic!(
703-
"no non-ZST fields in dyn receiver type {}",
703+
"no non-1-ZST fields in dyn receiver type {}",
704704
receiver.layout.ty
705705
)
706706
});

tests/ui/layout/debug.stderr

+2-2
Original file line numberDiff line numberDiff line change
@@ -117,13 +117,13 @@ error: layout_of(S) = Layout {
117117
fields: Arbitrary {
118118
offsets: [
119119
Size(0 bytes),
120-
Size(0 bytes),
120+
Size(8 bytes),
121121
Size(4 bytes),
122122
],
123123
memory_index: [
124-
1,
125124
0,
126125
2,
126+
1,
127127
],
128128
},
129129
largest_niche: None,

tests/ui/layout/enum.rs

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// normalize-stderr-test "pref: Align\([1-8] bytes\)" -> "pref: $$PREF_ALIGN"
2+
//! Various enum layout tests.
3+
4+
#![feature(rustc_attrs)]
5+
#![feature(never_type)]
6+
#![crate_type = "lib"]
7+
8+
#[rustc_layout(align)]
9+
enum UninhabitedVariantAlign { //~ERROR: abi: Align(2 bytes)
10+
A([u8; 32]),
11+
B([u16; 0], !), // make sure alignment in uninhabited fields is respected
12+
}
13+
14+
#[rustc_layout(size)]
15+
enum UninhabitedVariantSpace { //~ERROR: size: Size(16 bytes)
16+
A,
17+
B([u8; 15], !), // make sure there is space being reserved for this field.
18+
}

tests/ui/layout/enum.stderr

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
error: align: AbiAndPrefAlign { abi: Align(2 bytes), pref: $PREF_ALIGN }
2+
--> $DIR/enum.rs:9:1
3+
|
4+
LL | enum UninhabitedVariantAlign {
5+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
6+
7+
error: size: Size(16 bytes)
8+
--> $DIR/enum.rs:15:1
9+
|
10+
LL | enum UninhabitedVariantSpace {
11+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
12+
13+
error: aborting due to 2 previous errors
14+

tests/ui/layout/struct.rs

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
// normalize-stderr-test "pref: Align\([1-8] bytes\)" -> "pref: $$PREF_ALIGN"
2+
//! Various struct layout tests.
3+
4+
#![feature(rustc_attrs)]
5+
#![feature(never_type)]
6+
#![crate_type = "lib"]
7+
8+
#[rustc_layout(abi)]
9+
struct AlignedZstPreventsScalar(i16, [i32; 0]); //~ERROR: abi: Aggregate
10+
11+
#[rustc_layout(abi)]
12+
struct AlignedZstButStillScalar(i32, [i16; 0]); //~ERROR: abi: Scalar

tests/ui/layout/struct.stderr

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
error: abi: Aggregate { sized: true }
2+
--> $DIR/struct.rs:9:1
3+
|
4+
LL | struct AlignedZstPreventsScalar(i16, [i32; 0]);
5+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
6+
7+
error: abi: Scalar(Initialized { value: Int(I32, true), valid_range: 0..=4294967295 })
8+
--> $DIR/struct.rs:12:1
9+
|
10+
LL | struct AlignedZstButStillScalar(i32, [i16; 0]);
11+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
12+
13+
error: aborting due to 2 previous errors
14+

0 commit comments

Comments
 (0)