@@ -646,7 +646,10 @@ impl<Cx: HasDataLayout> LayoutCalculator<Cx> {
646
646
return Err ( LayoutCalculatorError :: ReprConflict ) ;
647
647
}
648
648
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 ) > {
650
653
if repr. inhibit_enum_layout_opt ( ) {
651
654
return None ;
652
655
}
@@ -658,16 +661,12 @@ impl<Cx: HasDataLayout> LayoutCalculator<Cx> {
658
661
let mut align = dl. aggregate_align ;
659
662
let mut max_repr_align = repr. align ;
660
663
let mut unadjusted_abi_align = align;
661
- let mut inhabited_variants = 0 ;
662
664
663
665
let mut variant_layouts = variants
664
666
. iter_enumerated ( )
665
667
. map ( |( j, v) | {
666
668
let mut st = self . univariant ( v, repr, StructKind :: AlwaysSized ) . ok ( ) ?;
667
669
st. variants = Variants :: Single { index : j, variants : None } ;
668
- if !st. uninhabited {
669
- inhabited_variants += 1 ;
670
- }
671
670
672
671
align = align. max ( st. align . abi ) ;
673
672
max_repr_align = max_repr_align. max ( st. max_repr_align ) ;
@@ -677,41 +676,66 @@ impl<Cx: HasDataLayout> LayoutCalculator<Cx> {
677
676
} )
678
677
. collect :: < Option < IndexVec < VariantIdx , _ > > > ( ) ?;
679
678
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.
688
683
let largest_variant_index = variant_layouts
689
684
. iter_enumerated ( )
690
685
. max_by_key ( |( _i, layout) | ( layout. size . bytes ( ) , !layout. uninhabited ) )
691
686
. map ( |( i, _layout) | i) ?;
692
687
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;
698
698
699
699
let all_indices = variants. indices ( ) ;
700
700
let needs_disc = |index : VariantIdx | {
701
701
index != largest_variant_index && !variant_layouts[ index] . uninhabited
702
702
} ;
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
+ } ;
705
733
706
734
let count =
707
735
( niche_variants. end ( ) . index ( ) as u128 - niche_variants. start ( ) . index ( ) as u128 ) + 1 ;
708
736
709
- // Use the largest niche in the largest variant.
710
- let niche = variant_layouts[ largest_variant_index] . largest_niche ?;
737
+ // Calculate the new niche.
711
738
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) ;
715
739
716
740
let all_variants_fit = variant_layouts. iter_enumerated_mut ( ) . all ( |( i, layout) | {
717
741
if i == largest_variant_index {
@@ -821,10 +845,19 @@ impl<Cx: HasDataLayout> LayoutCalculator<Cx> {
821
845
randomization_seed : combined_seed,
822
846
} ;
823
847
824
- Some ( layout)
848
+ Some ( ( layout, is_maybe_npo ) )
825
849
} ;
826
850
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
+ } ;
828
861
829
862
let discr_type = repr. discr_type ( ) ;
830
863
let discr_int = Integer :: from_attr ( dl, discr_type) ;
0 commit comments