Skip to content

Commit 018fe5a

Browse files
committed
Fix enum layout to use niche-filling layout in guaranteed-NPO cases, even when the niche is in an uninhabited variant.
1 parent 9452645 commit 018fe5a

File tree

3 files changed

+267
-28
lines changed

3 files changed

+267
-28
lines changed

compiler/rustc_abi/src/layout.rs

Lines changed: 60 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -646,7 +646,10 @@ impl<Cx: HasDataLayout> LayoutCalculator<Cx> {
646646
return Err(LayoutCalculatorError::ReprConflict);
647647
}
648648

649-
let calculate_niche_filling_layout = || -> Option<LayoutData<FieldIdx, VariantIdx>> {
649+
// Returns `(layout, is_this_maybe_npo)`.
650+
// If `is_this_maybe_npo` is true, this layout is preferred over the tagged and
651+
// no-tag layouts.
652+
let calculate_niche_filling_layout = || -> Option<(LayoutData<_, _>, bool)> {
650653
if repr.inhibit_enum_layout_opt() {
651654
return None;
652655
}
@@ -658,16 +661,12 @@ impl<Cx: HasDataLayout> LayoutCalculator<Cx> {
658661
let mut align = dl.aggregate_align;
659662
let mut max_repr_align = repr.align;
660663
let mut unadjusted_abi_align = align;
661-
let mut inhabited_variants = 0;
662664

663665
let mut variant_layouts = variants
664666
.iter_enumerated()
665667
.map(|(j, v)| {
666668
let mut st = self.univariant(v, repr, StructKind::AlwaysSized).ok()?;
667669
st.variants = Variants::Single { index: j, variants: None };
668-
if !st.uninhabited {
669-
inhabited_variants += 1;
670-
}
671670

672671
align = align.max(st.align.abi);
673672
max_repr_align = max_repr_align.max(st.max_repr_align);
@@ -677,41 +676,66 @@ impl<Cx: HasDataLayout> LayoutCalculator<Cx> {
677676
})
678677
.collect::<Option<IndexVec<VariantIdx, _>>>()?;
679678

680-
if inhabited_variants < 2 {
681-
// If there's only one inhabited variant, the no-tag layout will be equivalent to
682-
// what the niched layout would be. Returning `None` here lets us assume there is
683-
// another inhabited variant which simplifies the rest of the layout computation.
684-
return None;
685-
}
686-
687-
// Choose the largest variant, picking an inhabited variant in a tie
679+
// Choose the largest variant, picking an inhabited variant in a tie.
680+
// We still need to compute the niche-filling layout even if the largest variant
681+
// is uninhabited, because `Result<UninhabitedReprTransparentPtr, 1ZST>` needs to
682+
// still be NPO-optimized.
688683
let largest_variant_index = variant_layouts
689684
.iter_enumerated()
690685
.max_by_key(|(_i, layout)| (layout.size.bytes(), !layout.uninhabited))
691686
.map(|(i, _layout)| i)?;
692687

693-
if variant_layouts[largest_variant_index].uninhabited {
694-
// If the largest variant is uninhabited, then filling its niche would give
695-
// a worse layout than the tagged layout.
696-
return None;
697-
}
688+
// Use the largest niche in the largest variant.
689+
let niche = variant_layouts[largest_variant_index].largest_niche?;
690+
let niche_offset = niche.offset;
691+
let niche_size = niche.value.size(dl);
692+
let size = variant_layouts[largest_variant_index].size.align_to(align);
693+
// If the niche occupies the whole size of the enum,
694+
// this could be a case of NPO, so we should prefer this layout over the
695+
// tagged and no-tag layouts, even if it has a worse niche (due to the
696+
// niched variant being uninhabited)
697+
let is_maybe_npo = niche_size == size;
698698

699699
let all_indices = variants.indices();
700700
let needs_disc = |index: VariantIdx| {
701701
index != largest_variant_index && !variant_layouts[index].uninhabited
702702
};
703-
let niche_variants = all_indices.clone().find(|v| needs_disc(*v)).unwrap()
704-
..=all_indices.rev().find(|v| needs_disc(*v)).unwrap();
703+
let first_niche_variant = all_indices.clone().find(|v| needs_disc(*v));
704+
let last_niche_variant = all_indices.clone().rev().find(|v| needs_disc(*v));
705+
let niche_variants = match Option::zip(first_niche_variant, last_niche_variant) {
706+
Some((f, l)) => f..=l,
707+
// All non-largest variants are uninhabited.
708+
// If there are exactly 2 variants, and the largest variant's niche covers its
709+
// whole size, and the non-largest variant is 1-aligned and zero-sized,
710+
// this could be NPO.
711+
// However, that only happens if the non-largest variant is "absent",
712+
// which was already handled in `layout_of_struct_or_enum`, so we just debug_assert
713+
// that that is not the case.
714+
None if variants.len() == 2 && is_maybe_npo => {
715+
if cfg!(debug_assertions) {
716+
let non_largest_variant_index =
717+
all_indices.clone().find(|v| *v != largest_variant_index).unwrap();
718+
let non_largest_variant_layout =
719+
&variant_layouts[non_largest_variant_index];
720+
debug_assert!(
721+
non_largest_variant_layout.size > Size::ZERO
722+
|| non_largest_variant_layout.align.abi > Align::ONE
723+
);
724+
}
725+
// The non-largest variant is not 1-ZST, this cannot be NPO,
726+
// use the no-tag layout.
727+
return None;
728+
}
729+
// All non-largest variants are uninhabited.
730+
// This cannot be NPO, use the no-tag layout.
731+
None => return None,
732+
};
705733

706734
let count =
707735
(niche_variants.end().index() as u128 - niche_variants.start().index() as u128) + 1;
708736

709-
// Use the largest niche in the largest variant.
710-
let niche = variant_layouts[largest_variant_index].largest_niche?;
737+
// Caclulate the new niche.
711738
let (niche_start, niche_scalar) = niche.reserve(dl, count)?;
712-
let niche_offset = niche.offset;
713-
let niche_size = niche.value.size(dl);
714-
let size = variant_layouts[largest_variant_index].size.align_to(align);
715739

716740
let all_variants_fit = variant_layouts.iter_enumerated_mut().all(|(i, layout)| {
717741
if i == largest_variant_index {
@@ -821,10 +845,19 @@ impl<Cx: HasDataLayout> LayoutCalculator<Cx> {
821845
randomization_seed: combined_seed,
822846
};
823847

824-
Some(layout)
848+
Some((layout, is_maybe_npo))
825849
};
826850

827-
let niche_filling_layout = calculate_niche_filling_layout();
851+
let niche_filling_layout = match calculate_niche_filling_layout() {
852+
// If this is possibly NPO, we prefer this layout over the others,
853+
// even if they might have a better niche.
854+
// (They could never be smaller, since possibly-npo only occurs
855+
// when the niche fills the whole size of the enum.)
856+
Some((layout, true)) => return Ok(layout),
857+
// This is definitely not NPO, try the other layouts.
858+
Some((layout, false)) => Some(layout),
859+
None => None,
860+
};
828861

829862
let discr_type = repr.discr_type();
830863
let discr_int = Integer::from_attr(dl, discr_type);

tests/ui/layout/enum.rs

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,3 +115,95 @@ enum AllUninhabitedVariantsAlignedZst { //~ERROR: size: Size(0 bytes)
115115
A(AlignedNever),
116116
B(AlignedNever),
117117
}
118+
119+
120+
#[repr(transparent)]
121+
struct NPONever(&'static (), !);
122+
123+
#[repr(transparent)]
124+
struct NPONeverI16(std::num::NonZero<i16>, !);
125+
126+
// All of these should be NPO-optimized, despite uninhabitedness of one or more variants
127+
#[rustc_layout(abi)]
128+
type NPONever1 = Result<NPONever, ()>;
129+
//~^ ERROR: abi: Scalar(Initialized { value: Pointer
130+
131+
#[rustc_layout(abi)]
132+
type NPONever2 = Result<(), NPONever>;
133+
//~^ ERROR: abi: Scalar(Initialized { value: Pointer
134+
135+
#[rustc_layout(abi)]
136+
type NPONever3 = Result<NPONever, !>;
137+
//~^ ERROR: abi: Scalar(Initialized { value: Pointer
138+
139+
#[rustc_layout(abi)]
140+
type NPONever4 = Result<!, NPONever>;
141+
//~^ ERROR: abi: Scalar(Initialized { value: Pointer
142+
143+
#[rustc_layout(abi)]
144+
type NPONever5 = Result<&'static (), !>;
145+
//~^ ERROR: abi: Scalar(Initialized { value: Pointer
146+
147+
#[rustc_layout(abi)]
148+
type NPONever6 = Result<!, &'static ()>;
149+
//~^ ERROR: abi: Scalar(Initialized { value: Pointer
150+
151+
#[rustc_layout(abi)]
152+
type NPONever7 = Result<std::num::NonZero<i16>, !>;
153+
//~^ERROR: abi: Scalar(Initialized { value: Int(I16
154+
155+
#[rustc_layout(abi)]
156+
type NPONever8 = Result<!, std::num::NonZero<i16>>;
157+
//~^ERROR: abi: Scalar(Initialized { value: Int(I16
158+
159+
#[rustc_layout(abi)]
160+
type NPONever9 = Result<NPONeverI16, !>;
161+
//~^ERROR: abi: Scalar(Initialized { value: Int(I16
162+
163+
#[rustc_layout(abi)]
164+
type NPONever10 = Result<!, NPONeverI16>;
165+
//~^ERROR: abi: Scalar(Initialized { value: Int(I16
166+
167+
#[rustc_layout(abi)]
168+
type NPONever11 = Result<NPONeverI16, ()>;
169+
//~^ERROR: abi: Scalar(Initialized { value: Int(I16
170+
171+
#[rustc_layout(abi)]
172+
type NPONever12 = Result<(), NPONeverI16>;
173+
//~^ERROR: abi: Scalar(Initialized { value: Int(I16
174+
175+
#[rustc_layout(abi)]
176+
enum NPONever13 { //~ERROR: abi: Scalar(Initialized { value: Pointer
177+
A(!, &'static (), !),
178+
B((), !, [u8; 0]),
179+
}
180+
181+
#[rustc_layout(abi)]
182+
enum NPONever14 { //~ERROR: abi: Scalar(Initialized { value: Pointer
183+
A(!, &'static (), !),
184+
B((), (), [u8; 0]),
185+
}
186+
187+
#[rustc_layout(abi)]
188+
enum NPONever15 { //~ERROR: abi: Scalar(Initialized { value: Pointer
189+
A((), &'static (), ()),
190+
B((), !, [u8; 0]),
191+
}
192+
193+
194+
// These are not guaranteed to be NPO-optimized
195+
#[rustc_layout(abi)]
196+
type NotNPONever1 = Result<NPONever, NPONever>;
197+
//~^ERROR: abi: Scalar(
198+
199+
#[rustc_layout(abi)]
200+
type NotNPONever2 = Result<NPONever, AlignedNever>;
201+
//~^ERROR: abi: Memory
202+
203+
#[rustc_layout(abi)]
204+
type NotNPONever3 = Result<NPONever, &'static ()>;
205+
//~^ERROR: abi: Scalar(
206+
207+
#[rustc_layout(abi)]
208+
type NotNPONever4 = Result<&'static (), AlignedNever>;
209+
//~^ERROR: abi: Scalar(

tests/ui/layout/enum.stderr

Lines changed: 115 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -242,5 +242,119 @@ error: abi: Memory { sized: true }
242242
LL | enum AllUninhabitedVariantsAlignedZst {
243243
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
244244

245-
error: aborting due to 22 previous errors
245+
error: abi: Scalar(Initialized { value: Pointer(AddressSpace(0)), valid_range: (..=0) | (1..) })
246+
--> $DIR/enum.rs:128:1
247+
|
248+
LL | type NPONever1 = Result<NPONever, ()>;
249+
| ^^^^^^^^^^^^^^
250+
251+
error: abi: Scalar(Initialized { value: Pointer(AddressSpace(0)), valid_range: (..=0) | (1..) })
252+
--> $DIR/enum.rs:132:1
253+
|
254+
LL | type NPONever2 = Result<(), NPONever>;
255+
| ^^^^^^^^^^^^^^
256+
257+
error: abi: Scalar(Initialized { value: Pointer(AddressSpace(0)), valid_range: 1..=18446744073709551615 })
258+
--> $DIR/enum.rs:136:1
259+
|
260+
LL | type NPONever3 = Result<NPONever, !>;
261+
| ^^^^^^^^^^^^^^
262+
263+
error: abi: Scalar(Initialized { value: Pointer(AddressSpace(0)), valid_range: 1..=18446744073709551615 })
264+
--> $DIR/enum.rs:140:1
265+
|
266+
LL | type NPONever4 = Result<!, NPONever>;
267+
| ^^^^^^^^^^^^^^
268+
269+
error: abi: Scalar(Initialized { value: Pointer(AddressSpace(0)), valid_range: 1..=18446744073709551615 })
270+
--> $DIR/enum.rs:144:1
271+
|
272+
LL | type NPONever5 = Result<&'static (), !>;
273+
| ^^^^^^^^^^^^^^
274+
275+
error: abi: Scalar(Initialized { value: Pointer(AddressSpace(0)), valid_range: 1..=18446744073709551615 })
276+
--> $DIR/enum.rs:148:1
277+
|
278+
LL | type NPONever6 = Result<!, &'static ()>;
279+
| ^^^^^^^^^^^^^^
280+
281+
error: abi: Scalar(Initialized { value: Int(I16, true), valid_range: 1..=65535 })
282+
--> $DIR/enum.rs:152:1
283+
|
284+
LL | type NPONever7 = Result<std::num::NonZero<i16>, !>;
285+
| ^^^^^^^^^^^^^^
286+
287+
error: abi: Scalar(Initialized { value: Int(I16, true), valid_range: 1..=65535 })
288+
--> $DIR/enum.rs:156:1
289+
|
290+
LL | type NPONever8 = Result<!, std::num::NonZero<i16>>;
291+
| ^^^^^^^^^^^^^^
292+
293+
error: abi: Scalar(Initialized { value: Int(I16, true), valid_range: 1..=65535 })
294+
--> $DIR/enum.rs:160:1
295+
|
296+
LL | type NPONever9 = Result<NPONeverI16, !>;
297+
| ^^^^^^^^^^^^^^
298+
299+
error: abi: Scalar(Initialized { value: Int(I16, true), valid_range: 1..=65535 })
300+
--> $DIR/enum.rs:164:1
301+
|
302+
LL | type NPONever10 = Result<!, NPONeverI16>;
303+
| ^^^^^^^^^^^^^^^
304+
305+
error: abi: Scalar(Initialized { value: Int(I16, true), valid_range: (..=0) | (1..) })
306+
--> $DIR/enum.rs:168:1
307+
|
308+
LL | type NPONever11 = Result<NPONeverI16, ()>;
309+
| ^^^^^^^^^^^^^^^
310+
311+
error: abi: Scalar(Initialized { value: Int(I16, true), valid_range: (..=0) | (1..) })
312+
--> $DIR/enum.rs:172:1
313+
|
314+
LL | type NPONever12 = Result<(), NPONeverI16>;
315+
| ^^^^^^^^^^^^^^^
316+
317+
error: abi: Scalar(Initialized { value: Pointer(AddressSpace(0)), valid_range: 1..=18446744073709551615 })
318+
--> $DIR/enum.rs:176:1
319+
|
320+
LL | enum NPONever13 {
321+
| ^^^^^^^^^^^^^^^
322+
323+
error: abi: Scalar(Initialized { value: Pointer(AddressSpace(0)), valid_range: (..=0) | (1..) })
324+
--> $DIR/enum.rs:182:1
325+
|
326+
LL | enum NPONever14 {
327+
| ^^^^^^^^^^^^^^^
328+
329+
error: abi: Scalar(Initialized { value: Pointer(AddressSpace(0)), valid_range: 1..=18446744073709551615 })
330+
--> $DIR/enum.rs:188:1
331+
|
332+
LL | enum NPONever15 {
333+
| ^^^^^^^^^^^^^^^
334+
335+
error: abi: Scalar(Initialized { value: Int(I64, false), valid_range: 0..=0 })
336+
--> $DIR/enum.rs:196:1
337+
|
338+
LL | type NotNPONever1 = Result<NPONever, NPONever>;
339+
| ^^^^^^^^^^^^^^^^^
340+
341+
error: abi: Memory { sized: true }
342+
--> $DIR/enum.rs:200:1
343+
|
344+
LL | type NotNPONever2 = Result<NPONever, AlignedNever>;
345+
| ^^^^^^^^^^^^^^^^^
346+
347+
error: abi: Scalar(Initialized { value: Pointer(AddressSpace(0)), valid_range: 1..=18446744073709551615 })
348+
--> $DIR/enum.rs:204:1
349+
|
350+
LL | type NotNPONever3 = Result<NPONever, &'static ()>;
351+
| ^^^^^^^^^^^^^^^^^
352+
353+
error: abi: Scalar(Initialized { value: Pointer(AddressSpace(0)), valid_range: 1..=18446744073709551615 })
354+
--> $DIR/enum.rs:208:1
355+
|
356+
LL | type NotNPONever4 = Result<&'static (), AlignedNever>;
357+
| ^^^^^^^^^^^^^^^^^
358+
359+
error: aborting due to 41 previous errors
246360

0 commit comments

Comments
 (0)