1
Fork 0

Track (partial) niche information in NaiveLayout

Still more complexity, but this allows computing exact `NaiveLayout`s
for null-optimized enums, and thus allows calls like
`transmute::<Option<&T>, &U>()` to work in generic contexts.
This commit is contained in:
Moulins 2023-07-21 03:26:14 +02:00
parent 39cfe70e4f
commit 7f109086ee
5 changed files with 185 additions and 47 deletions

View file

@ -1,12 +1,14 @@
use rustc_middle::query::Providers;
use rustc_middle::ty::layout::{
IntegerExt, LayoutCx, LayoutError, LayoutOf, NaiveAbi, NaiveLayout, TyAndNaiveLayout,
IntegerExt, LayoutCx, LayoutError, LayoutOf, NaiveAbi, NaiveLayout, NaiveNiches,
TyAndNaiveLayout,
};
use rustc_middle::ty::{self, ReprOptions, Ty, TyCtxt, TypeVisitableExt};
use rustc_span::DUMMY_SP;
use rustc_target::abi::*;
use std::ops::Bound;
use crate::layout::{compute_array_count, ptr_metadata_scalar};
pub fn provide(providers: &mut Providers) {
@ -61,8 +63,9 @@ fn naive_layout_of_uncached<'tcx>(
let tcx = cx.tcx;
let dl = cx.data_layout();
let scalar = |value: Primitive| NaiveLayout {
let scalar = |niched: bool, value: Primitive| NaiveLayout {
abi: NaiveAbi::Scalar(value),
niches: if niched { NaiveNiches::Some } else { NaiveNiches::None },
size: value.size(dl),
align: value.align(dl).abi,
exact: true,
@ -105,26 +108,30 @@ fn naive_layout_of_uncached<'tcx>(
Ok(match *ty.kind() {
// Basic scalars
ty::Bool => scalar(Int(I8, false)),
ty::Char => scalar(Int(I32, false)),
ty::Int(ity) => scalar(Int(Integer::from_int_ty(dl, ity), true)),
ty::Uint(ity) => scalar(Int(Integer::from_uint_ty(dl, ity), false)),
ty::Float(fty) => scalar(match fty {
ty::FloatTy::F32 => F32,
ty::FloatTy::F64 => F64,
}),
ty::FnPtr(_) => scalar(Pointer(dl.instruction_address_space)),
ty::Bool => scalar(true, Int(I8, false)),
ty::Char => scalar(true, Int(I32, false)),
ty::Int(ity) => scalar(false, Int(Integer::from_int_ty(dl, ity), true)),
ty::Uint(ity) => scalar(false, Int(Integer::from_uint_ty(dl, ity), false)),
ty::Float(fty) => scalar(
false,
match fty {
ty::FloatTy::F32 => F32,
ty::FloatTy::F64 => F64,
},
),
ty::FnPtr(_) => scalar(true, Pointer(dl.instruction_address_space)),
// The never type.
ty::Never => NaiveLayout { abi: NaiveAbi::Uninhabited, ..NaiveLayout::EMPTY },
// Potentially-wide pointers.
ty::Ref(_, pointee, _) | ty::RawPtr(ty::TypeAndMut { ty: pointee, .. }) => {
let data_ptr = scalar(Pointer(AddressSpace::DATA));
let data_ptr = scalar(!ty.is_unsafe_ptr(), Pointer(AddressSpace::DATA));
if let Some(metadata) = ptr_metadata_scalar(cx, pointee)? {
// Effectively a (ptr, meta) tuple.
let meta = scalar(!metadata.is_always_valid(dl), metadata.primitive());
let l = data_ptr
.concat(&scalar(metadata.primitive()), dl)
.concat(&meta, dl)
.ok_or_else(|| error(cx, LayoutError::SizeOverflow(ty)))?;
l.pad_to_align(l.align)
} else {
@ -134,8 +141,9 @@ fn naive_layout_of_uncached<'tcx>(
}
ty::Dynamic(_, _, ty::DynStar) => {
let ptr = scalar(Pointer(AddressSpace::DATA));
ptr.concat(&ptr, dl).ok_or_else(|| error(cx, LayoutError::SizeOverflow(ty)))?
let ptr = scalar(false, Pointer(AddressSpace::DATA));
let vtable = scalar(true, Pointer(AddressSpace::DATA));
ptr.concat(&vtable, dl).ok_or_else(|| error(cx, LayoutError::SizeOverflow(ty)))?
}
// Arrays and slices.
@ -149,13 +157,16 @@ fn naive_layout_of_uncached<'tcx>(
.size
.checked_mul(count, cx)
.ok_or_else(|| error(cx, LayoutError::SizeOverflow(ty)))?,
niches: if count == 0 { NaiveNiches::None } else { element.niches },
..*element
}
}
ty::Slice(element) => {
let element = cx.naive_layout_of(element)?;
NaiveLayout { abi: NaiveAbi::Unsized, size: Size::ZERO, ..*element }
}
ty::Slice(element) => NaiveLayout {
abi: NaiveAbi::Unsized,
size: Size::ZERO,
niches: NaiveNiches::None,
..*cx.naive_layout_of(element)?
},
ty::FnDef(..) => NaiveLayout::EMPTY,
@ -166,7 +177,9 @@ fn naive_layout_of_uncached<'tcx>(
// FIXME(reference_niches): try to actually compute a reasonable layout estimate,
// without duplicating too much code from `generator_layout`.
ty::Generator(..) => NaiveLayout { exact: false, ..NaiveLayout::EMPTY },
ty::Generator(..) => {
NaiveLayout { exact: false, niches: NaiveNiches::Maybe, ..NaiveLayout::EMPTY }
}
ty::Closure(_, ref substs) => {
univariant(&mut substs.as_closure().upvar_tys(), &ReprOptions::default())?
@ -175,6 +188,7 @@ fn naive_layout_of_uncached<'tcx>(
ty::Tuple(tys) => univariant(&mut tys.iter(), &ReprOptions::default())?,
ty::Adt(def, substs) if def.is_union() => {
assert_eq!(def.variants().len(), 1, "union should have a single variant");
let repr = def.repr();
let pack = repr.pack.unwrap_or(Align::MAX);
if repr.pack.is_some() && repr.align.is_some() {
@ -182,7 +196,12 @@ fn naive_layout_of_uncached<'tcx>(
return Err(error(cx, LayoutError::Unknown(ty)));
}
let mut layout = NaiveLayout::EMPTY;
let mut layout = NaiveLayout {
// Unions never have niches.
niches: NaiveNiches::None,
..NaiveLayout::EMPTY
};
for f in &def.variants()[FIRST_VARIANT].fields {
let field = cx.naive_layout_of(f.ty(tcx, substs))?;
layout = layout.union(&field.packed(pack));
@ -201,24 +220,87 @@ fn naive_layout_of_uncached<'tcx>(
ty::Adt(def, substs) => {
let repr = def.repr();
let base = NaiveLayout {
// For simplicity, assume that any enum has its discriminant field (if it exists)
// niched inside one of the variants; this will underestimate the size (and sometimes
// alignment) of enums. We also doesn't compute exact alignment for SIMD structs.
// FIXME(reference_niches): Be smarter here.
// Also consider adding a special case for null-optimized enums, so that we can have
// `Option<&T>: PointerLike` in generic contexts.
exact: !def.is_enum() && !repr.simd(),
let mut layout = NaiveLayout {
// An ADT with no inhabited variants should have an uninhabited ABI.
abi: NaiveAbi::Uninhabited,
..NaiveLayout::EMPTY
};
let layout = def.variants().iter().try_fold(base, |layout, v| {
let mut empty_variants = 0;
for v in def.variants() {
let mut fields = v.fields.iter().map(|f| f.ty(tcx, substs));
let vlayout = univariant(&mut fields, &repr)?;
Ok(layout.union(&vlayout))
})?;
if vlayout.size == Size::ZERO && vlayout.exact {
empty_variants += 1;
} else {
// Remember the niches of the last seen variant.
layout.niches = vlayout.niches;
}
layout = layout.union(&vlayout);
}
if def.is_enum() {
let may_need_discr = match def.variants().len() {
0 | 1 => false,
// Simple Option-like niche optimization.
// Handling this special case allows enums like `Option<&T>`
// to be recognized as `PointerLike` and to be transmutable
// in generic contexts.
2 if empty_variants == 1 && layout.niches == NaiveNiches::Some => {
layout.niches = NaiveNiches::Maybe; // fill up the niche.
false
}
_ => true,
};
if may_need_discr || repr.inhibit_enum_layout_opt() {
// For simplicity, assume that the discriminant always get niched.
// This will be wrong in many cases, which will cause the size (and
// sometimes the alignment) to be underestimated.
// FIXME(reference_niches): Be smarter here.
layout.niches = NaiveNiches::Maybe;
layout = layout.inexact();
}
} else {
assert_eq!(def.variants().len(), 1, "struct should have a single variant");
// We don't compute exact alignment for SIMD structs.
if repr.simd() {
layout = layout.inexact();
}
// `UnsafeCell` hides all niches.
if def.is_unsafe_cell() {
layout.niches = NaiveNiches::None;
}
}
let valid_range = tcx.layout_scalar_valid_range(def.did());
if valid_range != (Bound::Unbounded, Bound::Unbounded) {
let get = |bound, default| match bound {
Bound::Unbounded => default,
Bound::Included(v) => v,
Bound::Excluded(_) => bug!("exclusive `layout_scalar_valid_range` bound"),
};
let valid_range = WrappingRange {
start: get(valid_range.0, 0),
// FIXME: this is wrong for scalar-pair ABIs. Fortunately, the
// only type this could currently affect is`NonNull<T: !Sized>`,
// and the `NaiveNiches` result still ends up correct.
end: get(valid_range.1, layout.size.unsigned_int_max()),
};
assert!(
valid_range.is_in_range_for(layout.size),
"`layout_scalar_valid_range` values are out of bounds",
);
if !valid_range.is_full_for(layout.size) {
layout.niches = NaiveNiches::Some;
}
}
layout.pad_to_align(layout.align)
}