Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix for #62691: use the largest niche across all fields #70411

Merged
merged 4 commits into from
Mar 26, 2020
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 22 additions & 14 deletions src/librustc/ty/layout.rs
Original file line number Diff line number Diff line change
Expand Up @@ -282,8 +282,6 @@ impl<'tcx> LayoutCx<'tcx, TyCtxt<'tcx>> {

let mut align = if pack.is_some() { dl.i8_align } else { dl.aggregate_align };

let mut sized = true;
let mut offsets = vec![Size::ZERO; fields.len()];
let mut inverse_memory_index: Vec<u32> = (0..fields.len() as u32).collect();

let mut optimize = !repr.inhibit_struct_field_reordering_opt();
Expand Down Expand Up @@ -320,6 +318,8 @@ impl<'tcx> LayoutCx<'tcx, TyCtxt<'tcx>> {
// At the bottom of this function, we invert `inverse_memory_index` to
// produce `memory_index` (see `invert_mapping`).

let mut sized = true;
let mut offsets = vec![Size::ZERO; fields.len()];
let mut offset = Size::ZERO;
let mut largest_niche = None;
let mut largest_niche_available = 0;
Expand Down Expand Up @@ -907,18 +907,26 @@ impl<'tcx> LayoutCx<'tcx, TyCtxt<'tcx>> {
let count = (niche_variants.end().as_u32()
- niche_variants.start().as_u32()
+ 1) as u128;
// FIXME(#62691) use the largest niche across all fields,
// not just the first one.
for (field_index, &field) in variants[i].iter().enumerate() {
let niche = match &field.largest_niche {
Some(niche) => niche,
_ => continue,
};
let (niche_start, niche_scalar) = match niche.reserve(self, count) {
Some(pair) => pair,
None => continue,
};

let mut niche_size = 0;
if let Some((field_index, niche, niche_start, niche_scalar)) =
variants[i].iter().enumerate().fold(None, |acc, (j, &field)| {
let niche = match &field.largest_niche {
Some(niche) => niche,
_ => return acc,
};
let ns = niche.available(dl);
if ns <= niche_size {
return acc;
}
match niche.reserve(self, count) {
Some(pair) => {
niche_size = ns;
Some((j, niche, pair.0, pair.1))
}
None => acc,
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You don't need this complication, just have .max_by_key(|(_, niche, _)| niche.available(dl)) before .and_then(|(_, niche)| Some((i, niche, niche.reserve(self, count)?))).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wanted to do that but that's not exactly the same thing. If it fails for the largest niche, but would have succeeded for a smaller niche, we would end up with None instead of using the smaller niche.
Now, maybe this cannot succeed for a smaller niche and fail for a bigger one so in that case that would be indeed best.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now, maybe this cannot succeed for a smaller niche and fail for a bigger one so in that case that would be indeed best.

Yeah, that's why we want bigger niches in general: they succeed strictly more often.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is what i've done now.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But looking at the code of Niche::reserve, it seems it can still fail if valid_range_contains despite being the largest niche while a smaller niche would work. or is that really unlikely?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The calculations are equivalent, I think I even double-checked them using bruteforce for small bitwidths.

niche.reserve(count) is checking niche.available() >= count but using values it has to compute anyway (i.e. the remaining validity range), instead of actually using available.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider sprinkling some comments here btw, and/or extracting functions :)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hopefully we can keep something close to the first commit, as it's more functional and way easier to explain as "find the largest niche among the fields" + "make sure it can fit our variant count".

})
{
let mut align = dl.aggregate_align;
let st = variants
.iter_enumerated()
Expand Down
7 changes: 7 additions & 0 deletions src/test/ui/type-sizes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,11 @@ enum NicheFilledEnumWithAbsentVariant {
C,
}

enum Option2<A, B> {
Some(A, B),
None
}

pub fn main() {
assert_eq!(size_of::<u8>(), 1 as usize);
assert_eq!(size_of::<u32>(), 4 as usize);
Expand Down Expand Up @@ -113,4 +118,6 @@ pub fn main() {

assert_eq!(size_of::<Option<Option<(bool, &())>>>(), size_of::<(bool, &())>());
assert_eq!(size_of::<Option<Option<(&(), bool)>>>(), size_of::<(bool, &())>());
assert_eq!(size_of::<Option<Option2<bool, &()>>>(), size_of::<(bool, &())>());
assert_eq!(size_of::<Option<Option2<&(), bool>>>(), size_of::<(bool, &())>());
}