@@ -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+ // Calculate 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) ;
0 commit comments