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

@ -655,6 +655,8 @@ impl std::ops::DerefMut for TyAndNaiveLayout<'_> {
#[derive(Copy, Clone, Debug, HashStable)]
pub struct NaiveLayout {
pub abi: NaiveAbi,
/// Niche information, required for tracking non-null enum optimizations.
pub niches: NaiveNiches,
/// An underestimate of the layout's size.
pub size: Size,
/// An underestimate of the layout's required alignment.
@ -663,13 +665,20 @@ pub struct NaiveLayout {
pub exact: bool,
}
#[derive(Copy, Clone, Debug, Eq, PartialEq, HashStable)]
pub enum NaiveNiches {
None,
Some,
Maybe,
}
#[derive(Copy, Clone, Debug, Eq, PartialEq, HashStable)]
pub enum NaiveAbi {
/// A scalar layout, always implies `exact`.
/// A scalar layout, always implies `exact` and a non-zero `size`.
Scalar(Primitive),
/// An uninhabited layout. (needed to properly track `Scalar`)
/// An uninhabited layout. (needed to properly track `Scalar` and niches)
Uninhabited,
/// An unsized aggregate. (needed to properly track `Scalar`)
/// An unsized aggregate. (needed to properly track `Scalar` and niches)
Unsized,
/// Any other sized layout.
Sized,
@ -687,8 +696,13 @@ impl NaiveAbi {
impl NaiveLayout {
/// The layout of an empty aggregate, e.g. `()`.
pub const EMPTY: Self =
Self { size: Size::ZERO, align: Align::ONE, exact: true, abi: NaiveAbi::Sized };
pub const EMPTY: Self = Self {
size: Size::ZERO,
align: Align::ONE,
exact: true,
abi: NaiveAbi::Sized,
niches: NaiveNiches::None,
};
/// Returns whether `self` is a valid approximation of the given full `layout`.
///
@ -699,12 +713,20 @@ impl NaiveLayout {
}
if let NaiveAbi::Scalar(prim) = self.abi {
assert!(self.exact);
if !matches!(layout.abi(), Abi::Scalar(s) if s.primitive() == prim) {
if !self.exact
|| self.size == Size::ZERO
|| !matches!(layout.abi(), Abi::Scalar(s) if s.primitive() == prim)
{
return false;
}
}
match (self.niches, layout.largest_niche()) {
(NaiveNiches::None, Some(_)) => return false,
(NaiveNiches::Some, None) => return false,
_ => (),
}
!self.exact || (self.size, self.align) == (layout.size(), layout.align().abi)
}
@ -745,6 +767,15 @@ impl NaiveLayout {
self
}
/// Artificially makes this layout inexact.
#[must_use]
#[inline]
pub fn inexact(mut self) -> Self {
self.abi = self.abi.as_aggregate();
self.exact = false;
self
}
/// Pads this layout so that its size is a multiple of `align`.
#[must_use]
#[inline]
@ -777,11 +808,18 @@ impl NaiveLayout {
// Default case.
(_, _) => Sized,
};
Some(Self { abi, size, align, exact })
let niches = match (self.niches, other.niches) {
(NaiveNiches::Some, _) | (_, NaiveNiches::Some) => NaiveNiches::Some,
(NaiveNiches::None, NaiveNiches::None) => NaiveNiches::None,
(_, _) => NaiveNiches::Maybe,
};
Some(Self { abi, size, align, exact, niches })
}
/// Returns the layout of `self` superposed with `other`, as in an `enum`
/// or an `union`.
///
/// Note: This always ignore niche information from `other`.
#[must_use]
#[inline]
pub fn union(&self, other: &Self) -> Self {
@ -793,7 +831,7 @@ impl NaiveLayout {
let abi = match (self.abi, other.abi) {
// The unsized ABI overrides everything.
(Unsized, _) | (_, Unsized) => Unsized,
// A scalar union must have a single non ZST-field.
// A scalar union must have a single non ZST-field...
(_, s @ Scalar(_)) if exact && self.size == Size::ZERO => s,
(s @ Scalar(_), _) if exact && other.size == Size::ZERO => s,
// ...or identical scalar fields.
@ -802,7 +840,7 @@ impl NaiveLayout {
(Uninhabited, Uninhabited) => Uninhabited,
(_, _) => Sized,
};
Self { abi, size, align, exact }
Self { abi, size, align, exact, niches: self.niches }
}
}