about summary refs log tree commit diff
diff options
context:
space:
mode:
authorThe 8472 <git@infinite-source.de>2023-04-19 22:14:28 +0200
committerThe 8472 <git@infinite-source.de>2023-04-28 23:08:54 +0200
commitafe106cdc8b5dbcbeedb292b87dc7d7ae58964f1 (patch)
treefc94a9f14c3f2592206e9d4bfea5a5a8e52a64be
parent67a835d755a97770edb320d315d542859b11f854 (diff)
downloadrust-afe106cdc8b5dbcbeedb292b87dc7d7ae58964f1.tar.gz
rust-afe106cdc8b5dbcbeedb292b87dc7d7ae58964f1.zip
[review] add comments, turn flag into enum
-rw-r--r--compiler/rustc_abi/src/layout.rs67
1 files changed, 44 insertions, 23 deletions
diff --git a/compiler/rustc_abi/src/layout.rs b/compiler/rustc_abi/src/layout.rs
index f15fb877d51..a49c9e58297 100644
--- a/compiler/rustc_abi/src/layout.rs
+++ b/compiler/rustc_abi/src/layout.rs
@@ -50,7 +50,7 @@ pub trait LayoutCalculator {
         repr: &ReprOptions,
         kind: StructKind,
     ) -> Option<LayoutS> {
-        let layout = univariant(self, dl, fields, repr, kind, true);
+        let layout = univariant(self, dl, fields, repr, kind, NicheBias::Start);
         // Enums prefer niches close to the beginning or the end of the variants so that other (smaller)
         // data-carrying variants can be packed into the space after/before the niche.
         // 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 {
                 // (e.g. a trailing bool) and there is tail padding. But it's non-trivial to get
                 // the unpadded size so we try anyway.
                 if fields.len() > 1 && head_space != 0 && tail_space > 0 {
-                    let alt_layout = univariant(self, dl, fields, repr, kind, false)
+                    let alt_layout = univariant(self, dl, fields, repr, kind, NicheBias::End)
                         .expect("alt layout should always work");
                     let niche = alt_layout
                         .largest_niche
@@ -776,13 +776,19 @@ pub trait LayoutCalculator {
     }
 }
 
+/// Determines towards which end of a struct layout optimizations will try to place the best niches.
+enum NicheBias {
+    Start,
+    End,
+}
+
 fn univariant(
     this: &(impl LayoutCalculator + ?Sized),
     dl: &TargetDataLayout,
     fields: &IndexSlice<FieldIdx, Layout<'_>>,
     repr: &ReprOptions,
     kind: StructKind,
-    niche_bias_start: bool,
+    niche_bias: NicheBias,
 ) -> Option<LayoutS> {
     let pack = repr.pack;
     let mut align = if pack.is_some() { dl.i8_align } else { dl.aggregate_align };
@@ -809,7 +815,10 @@ fn univariant(
         } else {
             let max_field_align = fields.iter().map(|f| f.align().abi.bytes()).max().unwrap_or(1);
             let any_niche = fields.iter().any(|f| f.largest_niche().is_some());
-            let effective_field_align = |layout: Layout<'_>| {
+
+            // Calculates a sort key to group fields by their alignment or possibly some size-derived
+            // pseudo-alignment.
+            let alignment_group_key = |layout: Layout<'_>| {
                 if let Some(pack) = pack {
                     // return the packed alignment in bytes
                     layout.align().abi.min(pack).bytes()
@@ -818,10 +827,13 @@ fn univariant(
                     // This is ok since `pack` applies to all fields equally.
                     // The calculation assumes that size is an integer multiple of align, except for ZSTs.
                     //
-                    // group [u8; 4] with align-4 or [u8; 6] with align-2 fields
                     let align = layout.align().abi.bytes();
                     let size = layout.size().bytes();
+                    // group [u8; 4] with align-4 or [u8; 6] with align-2 fields
                     let size_as_align = align.max(size).trailing_zeros();
+                    // Given `A(u8, [u8; 16])` and `B(bool, [u8; 16])` we want to bump the array
+                    // to the front in the first case (for aligned loads) but keep the bool in front
+                    // in the second case for its niches.
                     let size_as_align = if any_niche {
                         max_field_align.trailing_zeros().min(size_as_align)
                     } else {
@@ -833,21 +845,29 @@ fn univariant(
 
             match kind {
                 StructKind::AlwaysSized | StructKind::MaybeUnsized => {
+                    // Currently `LayoutS` only exposes a single niche so sorting is usually sufficient
+                    // to get one niche into the preferred position. If it ever supported multiple niches
+                    // then a more advanced pick-and-pack approach could provide better results.
+                    // But even for the single-niche cache it's not optimal. E.g. for
+                    // A(u32, (bool, u8), u16) it would be possible to move the bool to the front
+                    // but it would require packing the tuple together with the u16 to build a 4-byte
+                    // group so that the u32 can be placed after it without padding. This kind
+                    // of packing can't be achieved by sorting.
                     optimizing.sort_by_key(|&x| {
                         let f = fields[x];
                         let field_size = f.size().bytes();
                         let niche_size = f.largest_niche().map_or(0, |n| n.available(dl));
-                        let niche_size = if niche_bias_start {
-                            u128::MAX - niche_size // large niche first
-                        } else {
-                            niche_size // large niche last
+                        let niche_size_key = match niche_bias {
+                            // large niche first
+                            NicheBias::Start => !niche_size,
+                            // large niche last
+                            NicheBias::End => niche_size,
                         };
-                        let inner_niche_placement = if niche_bias_start {
-                            f.largest_niche().map_or(0, |n| n.offset.bytes())
-                        } else {
-                            f.largest_niche().map_or(0, |n| {
-                                field_size - n.value.size(dl).bytes() - n.offset.bytes()
-                            })
+                        let inner_niche_offset_key = match niche_bias {
+                            NicheBias::Start => f.largest_niche().map_or(0, |n| n.offset.bytes()),
+                            NicheBias::End => f.largest_niche().map_or(0, |n| {
+                                !(field_size - n.value.size(dl).bytes() - n.offset.bytes())
+                            }),
                         };
 
                         (
@@ -855,13 +875,13 @@ fn univariant(
                             // or two non-ZST fields. This helps Scalar/ScalarPair layouts.
                             !f.0.is_zst(),
                             // Then place largest alignments first.
-                            cmp::Reverse(effective_field_align(f)),
+                            cmp::Reverse(alignment_group_key(f)),
                             // Then prioritize niche placement within alignment group according to
                             // `niche_bias_start`.
-                            niche_size,
+                            niche_size_key,
                             // Then among fields with equally-sized niches prefer the ones
                             // closer to the start/end of the field.
-                            inner_niche_placement,
+                            inner_niche_offset_key,
                         )
                     });
                 }
@@ -874,7 +894,7 @@ fn univariant(
                     optimizing.sort_by_key(|&x| {
                         let f = fields[x];
                         let niche_size = f.largest_niche().map_or(0, |n| n.available(dl));
-                        (effective_field_align(f), niche_size)
+                        (alignment_group_key(f), niche_size)
                     });
                 }
             }
@@ -927,10 +947,11 @@ fn univariant(
 
         if let Some(mut niche) = field.largest_niche() {
             let available = niche.available(dl);
-            let prefer_new_niche = if niche_bias_start {
-                available > largest_niche_available
-            } else {
-                available >= largest_niche_available
+            // Pick up larger niches.
+            let prefer_new_niche = match niche_bias {
+                NicheBias::Start => available > largest_niche_available,
+                // if there are several niches of the same size then pick the last one
+                NicheBias::End => available >= largest_niche_available,
             };
             if prefer_new_niche {
                 largest_niche_available = available;