@@ -50,7 +50,7 @@ pub trait LayoutCalculator {
50
50
repr : & ReprOptions ,
51
51
kind : StructKind ,
52
52
) -> Option < LayoutS > {
53
- let layout = univariant ( self , dl, fields, repr, kind, true ) ;
53
+ let layout = univariant ( self , dl, fields, repr, kind, NicheBias :: Start ) ;
54
54
// Enums prefer niches close to the beginning or the end of the variants so that other (smaller)
55
55
// data-carrying variants can be packed into the space after/before the niche.
56
56
// 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 {
66
66
// (e.g. a trailing bool) and there is tail padding. But it's non-trivial to get
67
67
// the unpadded size so we try anyway.
68
68
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 )
70
70
. expect ( "alt layout should always work" ) ;
71
71
let niche = alt_layout
72
72
. largest_niche
@@ -776,13 +776,19 @@ pub trait LayoutCalculator {
776
776
}
777
777
}
778
778
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
+
779
785
fn univariant (
780
786
this : & ( impl LayoutCalculator + ?Sized ) ,
781
787
dl : & TargetDataLayout ,
782
788
fields : & IndexSlice < FieldIdx , Layout < ' _ > > ,
783
789
repr : & ReprOptions ,
784
790
kind : StructKind ,
785
- niche_bias_start : bool ,
791
+ niche_bias : NicheBias ,
786
792
) -> Option < LayoutS > {
787
793
let pack = repr. pack ;
788
794
let mut align = if pack. is_some ( ) { dl. i8_align } else { dl. aggregate_align } ;
@@ -809,7 +815,10 @@ fn univariant(
809
815
} else {
810
816
let max_field_align = fields. iter ( ) . map ( |f| f. align ( ) . abi . bytes ( ) ) . max ( ) . unwrap_or ( 1 ) ;
811
817
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 < ' _ > | {
813
822
if let Some ( pack) = pack {
814
823
// return the packed alignment in bytes
815
824
layout. align ( ) . abi . min ( pack) . bytes ( )
@@ -818,10 +827,13 @@ fn univariant(
818
827
// This is ok since `pack` applies to all fields equally.
819
828
// The calculation assumes that size is an integer multiple of align, except for ZSTs.
820
829
//
821
- // group [u8; 4] with align-4 or [u8; 6] with align-2 fields
822
830
let align = layout. align ( ) . abi . bytes ( ) ;
823
831
let size = layout. size ( ) . bytes ( ) ;
832
+ // group [u8; 4] with align-4 or [u8; 6] with align-2 fields
824
833
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.
825
837
let size_as_align = if any_niche {
826
838
max_field_align. trailing_zeros ( ) . min ( size_as_align)
827
839
} else {
@@ -833,35 +845,43 @@ fn univariant(
833
845
834
846
match kind {
835
847
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.
836
856
optimizing. sort_by_key ( |& x| {
837
857
let f = fields[ x] ;
838
858
let field_size = f. size ( ) . bytes ( ) ;
839
859
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,
844
865
} ;
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
+ } ) ,
851
871
} ;
852
872
853
873
(
854
874
// Place ZSTs first to avoid "interesting offsets", especially with only one
855
875
// or two non-ZST fields. This helps Scalar/ScalarPair layouts.
856
876
!f. 0 . is_zst ( ) ,
857
877
// Then place largest alignments first.
858
- cmp:: Reverse ( effective_field_align ( f) ) ,
878
+ cmp:: Reverse ( alignment_group_key ( f) ) ,
859
879
// Then prioritize niche placement within alignment group according to
860
880
// `niche_bias_start`.
861
- niche_size ,
881
+ niche_size_key ,
862
882
// Then among fields with equally-sized niches prefer the ones
863
883
// closer to the start/end of the field.
864
- inner_niche_placement ,
884
+ inner_niche_offset_key ,
865
885
)
866
886
} ) ;
867
887
}
@@ -874,7 +894,7 @@ fn univariant(
874
894
optimizing. sort_by_key ( |& x| {
875
895
let f = fields[ x] ;
876
896
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)
878
898
} ) ;
879
899
}
880
900
}
@@ -927,10 +947,11 @@ fn univariant(
927
947
928
948
if let Some ( mut niche) = field. largest_niche ( ) {
929
949
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,
934
955
} ;
935
956
if prefer_new_niche {
936
957
largest_niche_available = available;
0 commit comments