Skip to content

Commit aa3a417

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

File tree

4 files changed

+99
-21
lines changed

4 files changed

+99
-21
lines changed

compiler/rustc_abi/src/layout.rs

Lines changed: 89 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -783,6 +783,9 @@ impl<Cx: HasDataLayout> LayoutCalculator<Cx> {
783783
});
784784
trace!(?largest_niche);
785785

786+
let single_variant_layout_eligible =
787+
!repr.inhibit_enum_layout_opt() && valid_discriminants.len() == 1;
788+
786789
// `max` is the last valid discriminant before the largest niche
787790
// `min` is the first valid discriminant after the largest niche
788791
let (max, min) = largest_niche
@@ -815,10 +818,15 @@ impl<Cx: HasDataLayout> LayoutCalculator<Cx> {
815818
}
816819

817820
// Create the set of structs that represent each variant.
821+
let mut single_inhabited_variant_no_tag_layout = None;
818822
let mut layout_variants = variants
819823
.iter_enumerated()
820824
.map(|(i, field_layouts)| {
821825
let uninhabited = field_layouts.iter().any(|f| f.is_uninhabited());
826+
if !uninhabited && single_variant_layout_eligible {
827+
single_inhabited_variant_no_tag_layout =
828+
Some((i, self.univariant(field_layouts, repr, StructKind::AlwaysSized)));
829+
}
822830
// We don't need to encode the tag in uninhabited variants in repr(Rust) enums
823831
let struct_kind = if uninhabited && !repr.inhibit_enum_layout_opt() {
824832
StructKind::AlwaysSized
@@ -845,6 +853,62 @@ impl<Cx: HasDataLayout> LayoutCalculator<Cx> {
845853
})
846854
.collect::<Result<IndexVec<VariantIdx, _>, _>>()?;
847855

856+
// If there is a single uninhabited variant, we can use it mostly unchanged as the layout,
857+
// without using a tag or niche.
858+
//
859+
// We do still need to modify it to make all the uninhabited variants fit so they
860+
// can be partially-initialized.
861+
//
862+
// We keep this as a prospective layout, and don't assume it's better than the tagged
863+
// layout and return it immediately; e.g. it's worse for `enum Foo { A, B(i32, !) }`
864+
// because it has no niche.
865+
let no_tag_layout = if let Some((single_inhabited_variant_idx, Ok(mut st))) =
866+
single_inhabited_variant_no_tag_layout
867+
{
868+
// Keep track of original variant layouts (including the inhabited one)
869+
// for `offset_of!`.
870+
let mut variants = layout_variants.clone();
871+
variants[single_inhabited_variant_idx] = st.clone();
872+
873+
// We know that every other variant is uninhabited, and thus does not have a
874+
// prefix for the tag, so we can use them to find the necessary size.
875+
for (idx, layout) in layout_variants.iter_enumerated() {
876+
if idx != single_inhabited_variant_idx {
877+
st.size = cmp::max(st.size, layout.size);
878+
st.align = st.align.max(layout.align);
879+
st.max_repr_align = st.max_repr_align.max(layout.max_repr_align);
880+
st.unadjusted_abi_align =
881+
st.unadjusted_abi_align.max(layout.unadjusted_abi_align);
882+
}
883+
}
884+
885+
// Align the maximum variant size to the largest alignment.
886+
st.size = st.size.align_to(st.align.abi);
887+
888+
// If the inhabited variant's layout would use a non-Memory BackendRepr,
889+
// but we made the enum layout bigger or more-aligned due to uninhabited variants,
890+
// force the enum to be BackendRepr::Memory.
891+
//
892+
// FIXME: does this need to care about `max_repr_align` and `unadjusted_abi_align`?
893+
// The untagged layout is only used for `repr(Rust)` enums,
894+
// so `max_repr_align` and `unadjusted_abi_align` might be irrelevant.
895+
if st.size != variants[single_inhabited_variant_idx].size
896+
|| st.align != variants[single_inhabited_variant_idx].align
897+
|| st.max_repr_align != variants[single_inhabited_variant_idx].max_repr_align
898+
|| st.unadjusted_abi_align
899+
!= variants[single_inhabited_variant_idx].unadjusted_abi_align
900+
{
901+
st.backend_repr = BackendRepr::Memory { sized: true };
902+
}
903+
904+
st.variants =
905+
Variants::Single { index: single_inhabited_variant_idx, variants: Some(variants) };
906+
907+
Some(st)
908+
} else {
909+
None
910+
};
911+
848912
// Align the maximum variant size to the largest alignment.
849913
size = size.align_to(align);
850914

@@ -1125,22 +1189,36 @@ impl<Cx: HasDataLayout> LayoutCalculator<Cx> {
11251189
randomization_seed: combined_seed,
11261190
};
11271191

1128-
let best_layout = match (tagged_layout, niche_filling_layout) {
1129-
(tl, Some(nl)) => {
1130-
// Pick the smaller layout; otherwise,
1131-
// pick the layout with the larger niche; otherwise,
1132-
// pick tagged as it has simpler codegen.
1192+
// Pick the smallest layout; otherwise,
1193+
// pick the layout with the largest niche; otherwise,
1194+
// pick no_tag as it has simpler codegen than tagged and niched; otherwise,
1195+
// pick tagged as it has simpler codegen than niched.
1196+
1197+
let better_layout_or_first =
1198+
|l1: LayoutData<FieldIdx, VariantIdx>, l2: LayoutData<FieldIdx, VariantIdx>| {
11331199
use cmp::Ordering::*;
11341200
let niche_size = |l: &LayoutData<FieldIdx, VariantIdx>| {
11351201
l.largest_niche.map_or(0, |n| n.available(dl))
11361202
};
1137-
match (tl.size.cmp(&nl.size), niche_size(&tl).cmp(&niche_size(&nl))) {
1138-
(Greater, _) => nl,
1139-
(Equal, Less) => nl,
1140-
_ => tl,
1203+
match (l1.size.cmp(&l2.size), niche_size(&l1).cmp(&niche_size(&l2))) {
1204+
(Greater, _) => l2,
1205+
(Equal, Less) => l2,
1206+
_ => l1,
11411207
}
1142-
}
1143-
(tl, None) => tl,
1208+
};
1209+
1210+
let best_layout = match niche_filling_layout {
1211+
None => tagged_layout,
1212+
// Prefer tagged over niched if they have the same size and niche size,
1213+
// as the tagged layout has simpler codegen.
1214+
Some(niched_layout) => better_layout_or_first(tagged_layout, niched_layout),
1215+
};
1216+
1217+
let best_layout = match no_tag_layout {
1218+
None => best_layout,
1219+
// Prefer no-tag over tagged/niched if they have the same size and niche size,
1220+
// as the no-tag layout has simpler codegen.
1221+
Some(no_tag_layout) => better_layout_or_first(no_tag_layout, best_layout),
11441222
};
11451223

11461224
Ok(best_layout)

tests/ui/consts/const-eval/ub-enum.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -102,8 +102,8 @@ const BAD_UNINHABITED_WITH_DATA2: Result<(i32, !), (i32, Never)> = unsafe { mem:
102102

103103
// All variants have same-size data but only one inhabited.
104104
// Use `0` as constant to make behavior endianness-independent.
105-
const GOOD_SEMIINHABITED_WITH_DATA1: Result<i32, (i32, !)> = unsafe { mem::transmute(0u64) };
106-
const GOOD_SEMIINHABITED_WITH_DATA2: Result<(i32, !), i32> = unsafe { mem::transmute(1u64) };
105+
const GOOD_SEMIINHABITED_WITH_DATA1: Result<i32, (i32, !)> = unsafe { mem::transmute(0u32) };
106+
const GOOD_SEMIINHABITED_WITH_DATA2: Result<(i32, !), i32> = unsafe { mem::transmute(0u32) };
107107

108108
const TEST_ICE_89765: () = {
109109
// This is a regression test for https://github.com/rust-lang/rust/issues/89765.

tests/ui/layout/enum.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,8 @@ enum AbsentVariantUntagged { //~ERROR: size: Size(4 bytes)
3535
// Even if uninhabited variants are not absent, the enum can still be laid out without
3636
// a tag.
3737
#[rustc_layout(size, abi)]
38-
enum UninhabitedVariantUntagged { //~ERROR: size: Size(8 bytes)
39-
//~^ ERROR: abi: ScalarPair(Initialized
38+
enum UninhabitedVariantUntagged { //~ERROR: size: Size(4 bytes)
39+
//~^ ERROR: abi: Scalar(Initialized
4040
A(i32),
4141
B(i32, !),
4242
}
@@ -53,8 +53,8 @@ enum UninhabitedVariantUntaggedBigger { //~ERROR: size: Size(8 bytes)
5353
}
5454

5555
#[rustc_layout(size, abi)]
56-
enum UninhabitedVariantWithNiche { //~ERROR: size: Size(3 bytes)
57-
//~^ERROR: abi: Memory
56+
enum UninhabitedVariantWithNiche { //~ERROR: size: Size(2 bytes)
57+
//~^ERROR: abi: ScalarPair(Initialized { value: Int(I8, false), valid_range: 0..=1 }, Initialized { value: Int(I8, true), valid_range: 0..=255 })
5858
A(i8, bool),
5959
B(u8, u8, !),
6060
}

tests/ui/layout/enum.stderr

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,13 +28,13 @@ error: abi: Scalar(Initialized { value: Int(I32, true), valid_range: 0..=4294967
2828
LL | enum AbsentVariantUntagged {
2929
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
3030

31-
error: size: Size(8 bytes)
31+
error: size: Size(4 bytes)
3232
--> $DIR/enum.rs:38:1
3333
|
3434
LL | enum UninhabitedVariantUntagged {
3535
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
3636

37-
error: abi: ScalarPair(Initialized { value: Int(I32, false), valid_range: 0..=0 }, Initialized { value: Int(I32, true), valid_range: 0..=4294967295 })
37+
error: abi: Scalar(Initialized { value: Int(I32, true), valid_range: 0..=4294967295 })
3838
--> $DIR/enum.rs:38:1
3939
|
4040
LL | enum UninhabitedVariantUntagged {
@@ -52,13 +52,13 @@ error: abi: ScalarPair(Initialized { value: Int(I8, false), valid_range: 0..=0 }
5252
LL | enum UninhabitedVariantUntaggedBigger {
5353
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
5454

55-
error: size: Size(3 bytes)
55+
error: size: Size(2 bytes)
5656
--> $DIR/enum.rs:56:1
5757
|
5858
LL | enum UninhabitedVariantWithNiche {
5959
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
6060

61-
error: abi: Memory { sized: true }
61+
error: abi: ScalarPair(Initialized { value: Int(I8, false), valid_range: 0..=1 }, Initialized { value: Int(I8, true), valid_range: 0..=255 })
6262
--> $DIR/enum.rs:56:1
6363
|
6464
LL | enum UninhabitedVariantWithNiche {

0 commit comments

Comments
 (0)