@@ -50,7 +50,7 @@ pub trait LayoutCalculator {
5050 repr : & ReprOptions ,
5151 kind : StructKind ,
5252 ) -> Option < LayoutS > {
53- let layout = univariant ( self , dl, fields, repr, kind, true ) ;
53+ let layout = univariant ( self , dl, fields, repr, kind, NicheBias :: Start ) ;
5454 // Enums prefer niches close to the beginning or the end of the variants so that other (smaller)
5555 // data-carrying variants can be packed into the space after/before the niche.
5656 // If the default field ordering does not give us a niche at the front then we do a second
@@ -66,7 +66,7 @@ pub trait LayoutCalculator {
6666 // (e.g. a trailing bool) and there is tail padding. But it's non-trivial to get
6767 // the unpadded size so we try anyway.
6868 if fields. len ( ) > 1 && head_space != 0 && tail_space > 0 {
69- let alt_layout = univariant ( self , dl, fields, repr, kind, false )
69+ let alt_layout = univariant ( self , dl, fields, repr, kind, NicheBias :: End )
7070 . expect ( "alt layout should always work" ) ;
7171 let niche = alt_layout
7272 . largest_niche
@@ -776,13 +776,19 @@ pub trait LayoutCalculator {
776776 }
777777}
778778
779+ /// Determines towards which end of a struct layout optimizations will try to place the best niches.
780+ enum NicheBias {
781+ Start ,
782+ End ,
783+ }
784+
779785fn univariant (
780786 this : & ( impl LayoutCalculator + ?Sized ) ,
781787 dl : & TargetDataLayout ,
782788 fields : & IndexSlice < FieldIdx , Layout < ' _ > > ,
783789 repr : & ReprOptions ,
784790 kind : StructKind ,
785- niche_bias_start : bool ,
791+ niche_bias : NicheBias ,
786792) -> Option < LayoutS > {
787793 let pack = repr. pack ;
788794 let mut align = if pack. is_some ( ) { dl. i8_align } else { dl. aggregate_align } ;
@@ -809,7 +815,10 @@ fn univariant(
809815 } else {
810816 let max_field_align = fields. iter ( ) . map ( |f| f. align ( ) . abi . bytes ( ) ) . max ( ) . unwrap_or ( 1 ) ;
811817 let any_niche = fields. iter ( ) . any ( |f| f. largest_niche ( ) . is_some ( ) ) ;
812- let effective_field_align = |layout : Layout < ' _ > | {
818+
819+ // Calculates a sort key to group fields by their alignment or possibly some size-derived
820+ // pseudo-alignment.
821+ let alignment_group_key = |layout : Layout < ' _ > | {
813822 if let Some ( pack) = pack {
814823 // return the packed alignment in bytes
815824 layout. align ( ) . abi . min ( pack) . bytes ( )
@@ -818,10 +827,13 @@ fn univariant(
818827 // This is ok since `pack` applies to all fields equally.
819828 // The calculation assumes that size is an integer multiple of align, except for ZSTs.
820829 //
821- // group [u8; 4] with align-4 or [u8; 6] with align-2 fields
822830 let align = layout. align ( ) . abi . bytes ( ) ;
823831 let size = layout. size ( ) . bytes ( ) ;
832+ // group [u8; 4] with align-4 or [u8; 6] with align-2 fields
824833 let size_as_align = align. max ( size) . trailing_zeros ( ) ;
834+ // Given `A(u8, [u8; 16])` and `B(bool, [u8; 16])` we want to bump the array
835+ // to the front in the first case (for aligned loads) but keep the bool in front
836+ // in the second case for its niches.
825837 let size_as_align = if any_niche {
826838 max_field_align. trailing_zeros ( ) . min ( size_as_align)
827839 } else {
@@ -833,35 +845,43 @@ fn univariant(
833845
834846 match kind {
835847 StructKind :: AlwaysSized | StructKind :: MaybeUnsized => {
848+ // Currently `LayoutS` only exposes a single niche so sorting is usually sufficient
849+ // to get one niche into the preferred position. If it ever supported multiple niches
850+ // then a more advanced pick-and-pack approach could provide better results.
851+ // But even for the single-niche cache it's not optimal. E.g. for
852+ // A(u32, (bool, u8), u16) it would be possible to move the bool to the front
853+ // but it would require packing the tuple together with the u16 to build a 4-byte
854+ // group so that the u32 can be placed after it without padding. This kind
855+ // of packing can't be achieved by sorting.
836856 optimizing. sort_by_key ( |& x| {
837857 let f = fields[ x] ;
838858 let field_size = f. size ( ) . bytes ( ) ;
839859 let niche_size = f. largest_niche ( ) . map_or ( 0 , |n| n. available ( dl) ) ;
840- let niche_size = if niche_bias_start {
841- u128:: MAX - niche_size // large niche first
842- } else {
843- niche_size // large niche last
860+ let niche_size_key = match niche_bias {
861+ // large niche first
862+ NicheBias :: Start => !niche_size,
863+ // large niche last
864+ NicheBias :: End => niche_size,
844865 } ;
845- let inner_niche_placement = if niche_bias_start {
846- f. largest_niche ( ) . map_or ( 0 , |n| n. offset . bytes ( ) )
847- } else {
848- f. largest_niche ( ) . map_or ( 0 , |n| {
849- field_size - n. value . size ( dl) . bytes ( ) - n. offset . bytes ( )
850- } )
866+ let inner_niche_offset_key = match niche_bias {
867+ NicheBias :: Start => f. largest_niche ( ) . map_or ( 0 , |n| n. offset . bytes ( ) ) ,
868+ NicheBias :: End => f. largest_niche ( ) . map_or ( 0 , |n| {
869+ !( field_size - n. value . size ( dl) . bytes ( ) - n. offset . bytes ( ) )
870+ } ) ,
851871 } ;
852872
853873 (
854874 // Place ZSTs first to avoid "interesting offsets", especially with only one
855875 // or two non-ZST fields. This helps Scalar/ScalarPair layouts.
856876 !f. 0 . is_zst ( ) ,
857877 // Then place largest alignments first.
858- cmp:: Reverse ( effective_field_align ( f) ) ,
878+ cmp:: Reverse ( alignment_group_key ( f) ) ,
859879 // Then prioritize niche placement within alignment group according to
860880 // `niche_bias_start`.
861- niche_size ,
881+ niche_size_key ,
862882 // Then among fields with equally-sized niches prefer the ones
863883 // closer to the start/end of the field.
864- inner_niche_placement ,
884+ inner_niche_offset_key ,
865885 )
866886 } ) ;
867887 }
@@ -874,7 +894,7 @@ fn univariant(
874894 optimizing. sort_by_key ( |& x| {
875895 let f = fields[ x] ;
876896 let niche_size = f. largest_niche ( ) . map_or ( 0 , |n| n. available ( dl) ) ;
877- ( effective_field_align ( f) , niche_size)
897+ ( alignment_group_key ( f) , niche_size)
878898 } ) ;
879899 }
880900 }
@@ -927,10 +947,11 @@ fn univariant(
927947
928948 if let Some ( mut niche) = field. largest_niche ( ) {
929949 let available = niche. available ( dl) ;
930- let prefer_new_niche = if niche_bias_start {
931- available > largest_niche_available
932- } else {
933- available >= largest_niche_available
950+ // Pick up larger niches.
951+ let prefer_new_niche = match niche_bias {
952+ NicheBias :: Start => available > largest_niche_available,
953+ // if there are several niches of the same size then pick the last one
954+ NicheBias :: End => available >= largest_niche_available,
934955 } ;
935956 if prefer_new_niche {
936957 largest_niche_available = available;
0 commit comments