Skip to content

Commit 7095fd5

Browse files
committed
Do not store tag for single-inhabited-variant enums (if this results in a better layout)
1 parent e45d6ab commit 7095fd5

File tree

1 file changed

+147
-11
lines changed

1 file changed

+147
-11
lines changed

compiler/rustc_abi/src/layout.rs

Lines changed: 147 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,54 @@ impl<Cx: HasDataLayout> LayoutCalculator<Cx> {
197197
})
198198
}
199199

200+
pub fn simd_type_for_scalar<FieldIdx: Idx, VariantIdx: Idx, F>(
201+
&self,
202+
element: Scalar,
203+
count: u64,
204+
repr_packed: bool,
205+
) -> LayoutCalculatorResult<FieldIdx, VariantIdx, F> {
206+
let elt = element;
207+
if count == 0 {
208+
return Err(LayoutCalculatorError::ZeroLengthSimdType);
209+
} else if count > crate::MAX_SIMD_LANES {
210+
return Err(LayoutCalculatorError::OversizedSimdType {
211+
max_lanes: crate::MAX_SIMD_LANES,
212+
});
213+
}
214+
215+
// Compute the size and alignment of the vector
216+
let dl = self.cx.data_layout();
217+
let size = elt
218+
.size(&self.cx)
219+
.checked_mul(count, dl)
220+
.ok_or_else(|| LayoutCalculatorError::SizeOverflow)?;
221+
let (repr, align) = if repr_packed && !count.is_power_of_two() {
222+
// Non-power-of-two vectors have padding up to the next power-of-two.
223+
// If we're a packed repr, remove the padding while keeping the alignment as close
224+
// to a vector as possible.
225+
(BackendRepr::Memory { sized: true }, AbiAlign { abi: Align::max_aligned_factor(size) })
226+
} else {
227+
(BackendRepr::SimdVector { element, count }, dl.llvmlike_vector_align(size))
228+
};
229+
let size = size.align_to(align.abi);
230+
231+
Ok(LayoutData {
232+
variants: Variants::Single { index: VariantIdx::new(0), variants: None },
233+
fields: FieldsShape::Arbitrary {
234+
offsets: [Size::ZERO].into(),
235+
memory_index: [0].into(),
236+
},
237+
backend_repr: repr,
238+
largest_niche: None,
239+
uninhabited: false,
240+
size,
241+
align,
242+
max_repr_align: None,
243+
unadjusted_abi_align: elt.align(&self.cx).abi,
244+
randomization_seed: (Hash64::new(count)),
245+
})
246+
}
247+
200248
/// Compute the layout for a coroutine.
201249
///
202250
/// This uses dedicated code instead of [`Self::layout_of_struct_or_enum`], as coroutine
@@ -809,6 +857,9 @@ impl<Cx: HasDataLayout> LayoutCalculator<Cx> {
809857
});
810858
trace!(?largest_niche);
811859

860+
let single_variant_layout_eligible =
861+
!repr.inhibit_enum_layout_opt() && valid_discriminants.len() == 1;
862+
812863
// `max` is the last valid discriminant before the largest niche
813864
// `min` is the first valid discriminant after the largest niche
814865
let (max, min) = largest_niche
@@ -841,10 +892,15 @@ impl<Cx: HasDataLayout> LayoutCalculator<Cx> {
841892
}
842893

843894
// Create the set of structs that represent each variant.
895+
let mut single_inhabited_variant_no_tag_layout = None;
844896
let mut layout_variants = variants
845897
.iter_enumerated()
846898
.map(|(i, field_layouts)| {
847899
let uninhabited = field_layouts.iter().any(|f| f.is_uninhabited());
900+
if !uninhabited && single_variant_layout_eligible {
901+
single_inhabited_variant_no_tag_layout =
902+
Some((i, self.univariant(field_layouts, repr, StructKind::AlwaysSized)));
903+
}
848904
// We don't need to encode the tag in uninhabited variants in repr(Rust) enums
849905
let struct_kind = if uninhabited && !repr.inhibit_enum_layout_opt() {
850906
StructKind::AlwaysSized
@@ -871,6 +927,72 @@ impl<Cx: HasDataLayout> LayoutCalculator<Cx> {
871927
})
872928
.collect::<Result<IndexVec<VariantIdx, _>, _>>()?;
873929

930+
// If there is a single uninhabited variant, we can use it mostly unchanged as the layout,
931+
// without using a tag or niche.
932+
//
933+
// We do still need to modify it to make all the uninhabited variants fit so they
934+
// can be partially-initialized.
935+
//
936+
// FIXME: We shouldn't assume this is better than the tagged layout; it's worse for
937+
// `enum Foo { A, B(i32, !) }` because it has no niche.
938+
let no_tag_layout = if single_variant_layout_eligible
939+
&& let Some((single_inhabited_variant_idx, Ok(mut st))) =
940+
single_inhabited_variant_no_tag_layout
941+
{
942+
// Keep track of original variant layouts (including the inhabited one)
943+
// for `offset_of!`.
944+
let mut variants = layout_variants.clone();
945+
variants[single_inhabited_variant_idx] = st.clone();
946+
947+
// We know that every other variant is uninhabited, and thus does not have a
948+
// prefix for the tag, so we can use them to find the necessary size.
949+
for (idx, layout) in layout_variants.iter_enumerated() {
950+
if idx != single_inhabited_variant_idx {
951+
st.size = cmp::max(st.size, layout.size);
952+
st.align = st.align.max(layout.align);
953+
st.max_repr_align = st.max_repr_align.max(layout.max_repr_align);
954+
st.unadjusted_abi_align =
955+
st.unadjusted_abi_align.max(layout.unadjusted_abi_align);
956+
}
957+
}
958+
959+
// Align the maximum variant size to the largest alignment.
960+
st.size = st.size.align_to(st.align.abi);
961+
962+
// If the inhabited variant's layout would use a non-Memory BackendRepr,
963+
// but we made it bigger or more-aligned due to uninhabited variants,
964+
// force it to be BackendRepr::Memory
965+
match st.backend_repr {
966+
BackendRepr::Scalar(..) | BackendRepr::ScalarPair(..) => {
967+
if st.backend_repr.scalar_size(&self.cx) != Some(st.size)
968+
|| st.backend_repr.scalar_align(&self.cx) != Some(st.align.abi)
969+
{
970+
st.backend_repr = BackendRepr::Memory { sized: true }
971+
}
972+
}
973+
BackendRepr::SimdVector { element, count } => {
974+
// FIXME: is there a better way to do this than making a copy of
975+
// `LayoutCalculator::simd_type` *just* for this?
976+
let vector_layout = self.simd_type_for_scalar::<FieldIdx, VariantIdx, _>(
977+
element,
978+
count,
979+
repr.packed(),
980+
)?;
981+
if vector_layout.size != st.size || vector_layout.align != st.align {
982+
st.backend_repr = BackendRepr::Memory { sized: true }
983+
}
984+
}
985+
BackendRepr::Memory { .. } => {}
986+
}
987+
988+
st.variants =
989+
Variants::Single { index: single_inhabited_variant_idx, variants: Some(variants) };
990+
991+
Some(st)
992+
} else {
993+
None
994+
};
995+
874996
// Align the maximum variant size to the largest alignment.
875997
size = size.align_to(align.abi);
876998

@@ -1151,22 +1273,36 @@ impl<Cx: HasDataLayout> LayoutCalculator<Cx> {
11511273
randomization_seed: combined_seed,
11521274
};
11531275

1154-
let best_layout = match (tagged_layout, niche_filling_layout) {
1155-
(tl, Some(nl)) => {
1156-
// Pick the smaller layout; otherwise,
1157-
// pick the layout with the larger niche; otherwise,
1158-
// pick tagged as it has simpler codegen.
1276+
// Pick the smallest layout; otherwise,
1277+
// pick the layout with the largest niche; otherwise,
1278+
// pick no_tag as it has simpler codegen than tagged and niched; otherwise,
1279+
// pick tagged as it has simpler codegen than niched.
1280+
1281+
let better_layout_or_first =
1282+
|l1: LayoutData<FieldIdx, VariantIdx>, l2: LayoutData<FieldIdx, VariantIdx>| {
11591283
use cmp::Ordering::*;
11601284
let niche_size = |l: &LayoutData<FieldIdx, VariantIdx>| {
11611285
l.largest_niche.map_or(0, |n| n.available(dl))
11621286
};
1163-
match (tl.size.cmp(&nl.size), niche_size(&tl).cmp(&niche_size(&nl))) {
1164-
(Greater, _) => nl,
1165-
(Equal, Less) => nl,
1166-
_ => tl,
1287+
match (l1.size.cmp(&l2.size), niche_size(&l1).cmp(&niche_size(&l2))) {
1288+
(Greater, _) => l2,
1289+
(Equal, Less) => l2,
1290+
_ => l1,
11671291
}
1168-
}
1169-
(tl, None) => tl,
1292+
};
1293+
1294+
let best_layout = match niche_filling_layout {
1295+
None => tagged_layout,
1296+
// Prefer tagged over niched if they have the same size and niche size,
1297+
// as the tagged layout has simpler codegen.
1298+
Some(niched_layout) => better_layout_or_first(tagged_layout, niched_layout),
1299+
};
1300+
1301+
let best_layout = match no_tag_layout {
1302+
None => best_layout,
1303+
// Prefer no-tag over tagged/niched if they have the same size and niche size,
1304+
// as the no-tag layout has simpler codegen.
1305+
Some(no_tag_layout) => better_layout_or_first(no_tag_layout, best_layout),
11701306
};
11711307

11721308
Ok(best_layout)

0 commit comments

Comments
 (0)