Skip to content

Commit faf2da3

Browse files
committed
try two different niche-placement strategies when layouting univariant structs
1 parent be8e67d commit faf2da3

File tree

2 files changed

+99
-7
lines changed

2 files changed

+99
-7
lines changed

compiler/rustc_abi/src/layout.rs

Lines changed: 70 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,42 @@ pub trait LayoutCalculator {
4949
repr: &ReprOptions,
5050
kind: StructKind,
5151
) -> Option<LayoutS> {
52-
univariant(self, dl, fields, repr, kind)
52+
let layout = univariant(self, dl, fields, repr, kind, true);
53+
// Enums prefer niches close to the beginning or the end of the variants so that other (smaller)
54+
// data-carrying variants can be packed into the space after/before the niche.
55+
// If the default field ordering does not give us a niche at the front then we do a second
56+
// run and bias niches to the right and then check which one is closer to one of the struct's
57+
// edges.
58+
if let Some(layout) = &layout {
59+
if let Some(niche) = layout.largest_niche {
60+
let head_space = niche.offset.bytes();
61+
let niche_length = niche.value.size(dl).bytes();
62+
let tail_space = layout.size.bytes() - head_space - niche_length;
63+
64+
// This may end up doing redundant work if the niche is already in the last field
65+
// (e.g. a trailing bool) and there is tail padding. But it's non-trivial to get
66+
// the unpadded size so we try anyway.
67+
if fields.len() > 1 && head_space != 0 && tail_space > 0 {
68+
let alt_layout = univariant(self, dl, fields, repr, kind, false)
69+
.expect("alt layout should always work");
70+
let niche = alt_layout
71+
.largest_niche
72+
.expect("alt layout should have a niche like the regular one");
73+
let alt_head_space = niche.offset.bytes();
74+
let alt_niche_len = niche.value.size(dl).bytes();
75+
76+
debug_assert_eq!(layout.size.bytes(), alt_layout.size.bytes());
77+
78+
let prefer_alt_layout =
79+
alt_head_space > head_space && alt_head_space > tail_space;
80+
81+
if prefer_alt_layout {
82+
return Some(alt_layout);
83+
}
84+
}
85+
}
86+
}
87+
layout
5388
}
5489

5590
fn layout_of_never_type(&self) -> LayoutS {
@@ -728,6 +763,7 @@ fn univariant(
728763
fields: &IndexSlice<FieldIdx, Layout<'_>>,
729764
repr: &ReprOptions,
730765
kind: StructKind,
766+
niche_bias_start: bool,
731767
) -> Option<LayoutS> {
732768
let pack = repr.pack;
733769
let mut align = if pack.is_some() { dl.i8_align } else { dl.aggregate_align };
@@ -768,12 +804,35 @@ fn univariant(
768804
match kind {
769805
StructKind::AlwaysSized | StructKind::MaybeUnsized => {
770806
optimizing.sort_by_key(|&x| {
771-
// Place ZSTs first to avoid "interesting offsets",
772-
// especially with only one or two non-ZST fields.
773-
// Then place largest alignments first, largest niches within an alignment group last
774807
let f = fields[x];
808+
let field_size = f.size().bytes();
775809
let niche_size = f.largest_niche().map_or(0, |n| n.available(dl));
776-
(!f.0.is_zst(), cmp::Reverse(effective_field_align(f)), niche_size)
810+
let niche_size = if niche_bias_start {
811+
u128::MAX - niche_size // large niche first
812+
} else {
813+
niche_size // large niche last
814+
};
815+
let inner_niche_placement = if niche_bias_start {
816+
f.largest_niche().map_or(0, |n| n.offset.bytes())
817+
} else {
818+
f.largest_niche().map_or(0, |n| {
819+
field_size - n.value.size(dl).bytes() - n.offset.bytes()
820+
})
821+
};
822+
823+
(
824+
// Place ZSTs first to avoid "interesting offsets", especially with only one
825+
// or two non-ZST fields. This helps Scalar/ScalarPair layouts.
826+
!f.0.is_zst(),
827+
// Then place largest alignments first.
828+
cmp::Reverse(effective_field_align(f)),
829+
// Then prioritize niche placement within alignment group according to
830+
// `niche_bias_start`.
831+
niche_size,
832+
// Then among fields with equally-sized niches prefer the ones
833+
// closer to the start/end of the field.
834+
inner_niche_placement,
835+
)
777836
});
778837
}
779838

@@ -838,7 +897,12 @@ fn univariant(
838897

839898
if let Some(mut niche) = field.largest_niche() {
840899
let available = niche.available(dl);
841-
if available > largest_niche_available {
900+
let prefer_new_niche = if niche_bias_start {
901+
available > largest_niche_available
902+
} else {
903+
available >= largest_niche_available
904+
};
905+
if prefer_new_niche {
842906
largest_niche_available = available;
843907
niche.offset += offset;
844908
largest_niche = Some(niche);

tests/ui/structs-enums/type-sizes.rs

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,14 @@
44
#![allow(dead_code)]
55
#![feature(never_type)]
66
#![feature(pointer_is_aligned)]
7+
#![feature(ptr_from_ref)]
8+
#![feature(strict_provenance)]
79

810
use std::mem::size_of;
9-
use std::num::NonZeroU8;
11+
use std::num::{NonZeroU8, NonZeroU16};
12+
use std::ptr;
13+
use std::ptr::NonNull;
14+
use std::borrow::Cow;
1015

1116
struct t {a: u8, b: i8}
1217
struct u {a: u8, b: i8, c: u8}
@@ -181,6 +186,17 @@ struct Reorder2 {
181186
ary: [u8; 6],
182187
}
183188

189+
// standins for std types which we want to be laid out in a reasonable way
190+
struct RawVecDummy {
191+
ptr: NonNull<u8>,
192+
cap: usize,
193+
}
194+
195+
struct VecDummy {
196+
r: RawVecDummy,
197+
len: usize,
198+
}
199+
184200
pub fn main() {
185201
assert_eq!(size_of::<u8>(), 1 as usize);
186202
assert_eq!(size_of::<u32>(), 4 as usize);
@@ -270,4 +286,16 @@ pub fn main() {
270286
let v = Reorder2 {a: 0, b: 0, ary: [0; 6]};
271287
assert_eq!(size_of::<Reorder2>(), 10);
272288
assert!((&v.ary).as_ptr().is_aligned_to(2), "[u8; 6] should group with align-2 fields");
289+
290+
let v = VecDummy { r: RawVecDummy { ptr: NonNull::dangling(), cap: 0 }, len: 1 };
291+
assert_eq!(ptr::from_ref(&v), ptr::from_ref(&v.r.ptr).cast(),
292+
"sort niches to the front where possible");
293+
294+
// Ideal layouts: (bool, u8, NonZeroU16) or (NonZeroU16, u8, bool)
295+
// Currently the layout algorithm will choose the latter because it doesn't attempt
296+
// to aggregate multiple smaller fields to move a niche before a higher-alignment one.
297+
let b = BoolInTheMiddle( NonZeroU16::new(1).unwrap(), true, 0);
298+
assert!(ptr::from_ref(&b.1).addr() > ptr::from_ref(&b.2).addr());
299+
300+
assert_eq!(size_of::<Cow<'static, str>>(), size_of::<String>());
273301
}

0 commit comments

Comments
 (0)