Skip to content

Commit 7f10908

Browse files
committed
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.
1 parent 39cfe70 commit 7f10908

File tree

5 files changed

+185
-47
lines changed

5 files changed

+185
-47
lines changed

compiler/rustc_abi/src/lib.rs

+8-2
Original file line numberDiff line numberDiff line change
@@ -1062,9 +1062,15 @@ impl WrappingRange {
10621062
/// Returns `true` if `size` completely fills the range.
10631063
#[inline]
10641064
pub fn is_full_for(&self, size: Size) -> bool {
1065+
debug_assert!(self.is_in_range_for(size));
1066+
self.start == (self.end.wrapping_add(1) & size.unsigned_int_max())
1067+
}
1068+
1069+
/// Returns `true` if the range is valid for `size`.
1070+
#[inline(always)]
1071+
pub fn is_in_range_for(&self, size: Size) -> bool {
10651072
let max_value = size.unsigned_int_max();
1066-
debug_assert!(self.start <= max_value && self.end <= max_value);
1067-
self.start == (self.end.wrapping_add(1) & max_value)
1073+
self.start <= max_value && self.end <= max_value
10681074
}
10691075
}
10701076

compiler/rustc_middle/src/ty/layout.rs

+48-10
Original file line numberDiff line numberDiff line change
@@ -655,6 +655,8 @@ impl std::ops::DerefMut for TyAndNaiveLayout<'_> {
655655
#[derive(Copy, Clone, Debug, HashStable)]
656656
pub struct NaiveLayout {
657657
pub abi: NaiveAbi,
658+
/// Niche information, required for tracking non-null enum optimizations.
659+
pub niches: NaiveNiches,
658660
/// An underestimate of the layout's size.
659661
pub size: Size,
660662
/// An underestimate of the layout's required alignment.
@@ -663,13 +665,20 @@ pub struct NaiveLayout {
663665
pub exact: bool,
664666
}
665667

668+
#[derive(Copy, Clone, Debug, Eq, PartialEq, HashStable)]
669+
pub enum NaiveNiches {
670+
None,
671+
Some,
672+
Maybe,
673+
}
674+
666675
#[derive(Copy, Clone, Debug, Eq, PartialEq, HashStable)]
667676
pub enum NaiveAbi {
668-
/// A scalar layout, always implies `exact`.
677+
/// A scalar layout, always implies `exact` and a non-zero `size`.
669678
Scalar(Primitive),
670-
/// An uninhabited layout. (needed to properly track `Scalar`)
679+
/// An uninhabited layout. (needed to properly track `Scalar` and niches)
671680
Uninhabited,
672-
/// An unsized aggregate. (needed to properly track `Scalar`)
681+
/// An unsized aggregate. (needed to properly track `Scalar` and niches)
673682
Unsized,
674683
/// Any other sized layout.
675684
Sized,
@@ -687,8 +696,13 @@ impl NaiveAbi {
687696

688697
impl NaiveLayout {
689698
/// The layout of an empty aggregate, e.g. `()`.
690-
pub const EMPTY: Self =
691-
Self { size: Size::ZERO, align: Align::ONE, exact: true, abi: NaiveAbi::Sized };
699+
pub const EMPTY: Self = Self {
700+
size: Size::ZERO,
701+
align: Align::ONE,
702+
exact: true,
703+
abi: NaiveAbi::Sized,
704+
niches: NaiveNiches::None,
705+
};
692706

693707
/// Returns whether `self` is a valid approximation of the given full `layout`.
694708
///
@@ -699,12 +713,20 @@ impl NaiveLayout {
699713
}
700714

701715
if let NaiveAbi::Scalar(prim) = self.abi {
702-
assert!(self.exact);
703-
if !matches!(layout.abi(), Abi::Scalar(s) if s.primitive() == prim) {
716+
if !self.exact
717+
|| self.size == Size::ZERO
718+
|| !matches!(layout.abi(), Abi::Scalar(s) if s.primitive() == prim)
719+
{
704720
return false;
705721
}
706722
}
707723

724+
match (self.niches, layout.largest_niche()) {
725+
(NaiveNiches::None, Some(_)) => return false,
726+
(NaiveNiches::Some, None) => return false,
727+
_ => (),
728+
}
729+
708730
!self.exact || (self.size, self.align) == (layout.size(), layout.align().abi)
709731
}
710732

@@ -745,6 +767,15 @@ impl NaiveLayout {
745767
self
746768
}
747769

770+
/// Artificially makes this layout inexact.
771+
#[must_use]
772+
#[inline]
773+
pub fn inexact(mut self) -> Self {
774+
self.abi = self.abi.as_aggregate();
775+
self.exact = false;
776+
self
777+
}
778+
748779
/// Pads this layout so that its size is a multiple of `align`.
749780
#[must_use]
750781
#[inline]
@@ -777,11 +808,18 @@ impl NaiveLayout {
777808
// Default case.
778809
(_, _) => Sized,
779810
};
780-
Some(Self { abi, size, align, exact })
811+
let niches = match (self.niches, other.niches) {
812+
(NaiveNiches::Some, _) | (_, NaiveNiches::Some) => NaiveNiches::Some,
813+
(NaiveNiches::None, NaiveNiches::None) => NaiveNiches::None,
814+
(_, _) => NaiveNiches::Maybe,
815+
};
816+
Some(Self { abi, size, align, exact, niches })
781817
}
782818

783819
/// Returns the layout of `self` superposed with `other`, as in an `enum`
784820
/// or an `union`.
821+
///
822+
/// Note: This always ignore niche information from `other`.
785823
#[must_use]
786824
#[inline]
787825
pub fn union(&self, other: &Self) -> Self {
@@ -793,7 +831,7 @@ impl NaiveLayout {
793831
let abi = match (self.abi, other.abi) {
794832
// The unsized ABI overrides everything.
795833
(Unsized, _) | (_, Unsized) => Unsized,
796-
// A scalar union must have a single non ZST-field.
834+
// A scalar union must have a single non ZST-field...
797835
(_, s @ Scalar(_)) if exact && self.size == Size::ZERO => s,
798836
(s @ Scalar(_), _) if exact && other.size == Size::ZERO => s,
799837
// ...or identical scalar fields.
@@ -802,7 +840,7 @@ impl NaiveLayout {
802840
(Uninhabited, Uninhabited) => Uninhabited,
803841
(_, _) => Sized,
804842
};
805-
Self { abi, size, align, exact }
843+
Self { abi, size, align, exact, niches: self.niches }
806844
}
807845
}
808846

compiler/rustc_ty_utils/src/layout_naive.rs

+115-33
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
use rustc_middle::query::Providers;
22
use rustc_middle::ty::layout::{
3-
IntegerExt, LayoutCx, LayoutError, LayoutOf, NaiveAbi, NaiveLayout, TyAndNaiveLayout,
3+
IntegerExt, LayoutCx, LayoutError, LayoutOf, NaiveAbi, NaiveLayout, NaiveNiches,
4+
TyAndNaiveLayout,
45
};
56
use rustc_middle::ty::{self, ReprOptions, Ty, TyCtxt, TypeVisitableExt};
6-
77
use rustc_span::DUMMY_SP;
88
use rustc_target::abi::*;
99

10+
use std::ops::Bound;
11+
1012
use crate::layout::{compute_array_count, ptr_metadata_scalar};
1113

1214
pub fn provide(providers: &mut Providers) {
@@ -61,8 +63,9 @@ fn naive_layout_of_uncached<'tcx>(
6163
let tcx = cx.tcx;
6264
let dl = cx.data_layout();
6365

64-
let scalar = |value: Primitive| NaiveLayout {
66+
let scalar = |niched: bool, value: Primitive| NaiveLayout {
6567
abi: NaiveAbi::Scalar(value),
68+
niches: if niched { NaiveNiches::Some } else { NaiveNiches::None },
6669
size: value.size(dl),
6770
align: value.align(dl).abi,
6871
exact: true,
@@ -105,26 +108,30 @@ fn naive_layout_of_uncached<'tcx>(
105108

106109
Ok(match *ty.kind() {
107110
// Basic scalars
108-
ty::Bool => scalar(Int(I8, false)),
109-
ty::Char => scalar(Int(I32, false)),
110-
ty::Int(ity) => scalar(Int(Integer::from_int_ty(dl, ity), true)),
111-
ty::Uint(ity) => scalar(Int(Integer::from_uint_ty(dl, ity), false)),
112-
ty::Float(fty) => scalar(match fty {
113-
ty::FloatTy::F32 => F32,
114-
ty::FloatTy::F64 => F64,
115-
}),
116-
ty::FnPtr(_) => scalar(Pointer(dl.instruction_address_space)),
111+
ty::Bool => scalar(true, Int(I8, false)),
112+
ty::Char => scalar(true, Int(I32, false)),
113+
ty::Int(ity) => scalar(false, Int(Integer::from_int_ty(dl, ity), true)),
114+
ty::Uint(ity) => scalar(false, Int(Integer::from_uint_ty(dl, ity), false)),
115+
ty::Float(fty) => scalar(
116+
false,
117+
match fty {
118+
ty::FloatTy::F32 => F32,
119+
ty::FloatTy::F64 => F64,
120+
},
121+
),
122+
ty::FnPtr(_) => scalar(true, Pointer(dl.instruction_address_space)),
117123

118124
// The never type.
119125
ty::Never => NaiveLayout { abi: NaiveAbi::Uninhabited, ..NaiveLayout::EMPTY },
120126

121127
// Potentially-wide pointers.
122128
ty::Ref(_, pointee, _) | ty::RawPtr(ty::TypeAndMut { ty: pointee, .. }) => {
123-
let data_ptr = scalar(Pointer(AddressSpace::DATA));
129+
let data_ptr = scalar(!ty.is_unsafe_ptr(), Pointer(AddressSpace::DATA));
124130
if let Some(metadata) = ptr_metadata_scalar(cx, pointee)? {
125131
// Effectively a (ptr, meta) tuple.
132+
let meta = scalar(!metadata.is_always_valid(dl), metadata.primitive());
126133
let l = data_ptr
127-
.concat(&scalar(metadata.primitive()), dl)
134+
.concat(&meta, dl)
128135
.ok_or_else(|| error(cx, LayoutError::SizeOverflow(ty)))?;
129136
l.pad_to_align(l.align)
130137
} else {
@@ -134,8 +141,9 @@ fn naive_layout_of_uncached<'tcx>(
134141
}
135142

136143
ty::Dynamic(_, _, ty::DynStar) => {
137-
let ptr = scalar(Pointer(AddressSpace::DATA));
138-
ptr.concat(&ptr, dl).ok_or_else(|| error(cx, LayoutError::SizeOverflow(ty)))?
144+
let ptr = scalar(false, Pointer(AddressSpace::DATA));
145+
let vtable = scalar(true, Pointer(AddressSpace::DATA));
146+
ptr.concat(&vtable, dl).ok_or_else(|| error(cx, LayoutError::SizeOverflow(ty)))?
139147
}
140148

141149
// Arrays and slices.
@@ -149,13 +157,16 @@ fn naive_layout_of_uncached<'tcx>(
149157
.size
150158
.checked_mul(count, cx)
151159
.ok_or_else(|| error(cx, LayoutError::SizeOverflow(ty)))?,
160+
niches: if count == 0 { NaiveNiches::None } else { element.niches },
152161
..*element
153162
}
154163
}
155-
ty::Slice(element) => {
156-
let element = cx.naive_layout_of(element)?;
157-
NaiveLayout { abi: NaiveAbi::Unsized, size: Size::ZERO, ..*element }
158-
}
164+
ty::Slice(element) => NaiveLayout {
165+
abi: NaiveAbi::Unsized,
166+
size: Size::ZERO,
167+
niches: NaiveNiches::None,
168+
..*cx.naive_layout_of(element)?
169+
},
159170

160171
ty::FnDef(..) => NaiveLayout::EMPTY,
161172

@@ -166,7 +177,9 @@ fn naive_layout_of_uncached<'tcx>(
166177

167178
// FIXME(reference_niches): try to actually compute a reasonable layout estimate,
168179
// without duplicating too much code from `generator_layout`.
169-
ty::Generator(..) => NaiveLayout { exact: false, ..NaiveLayout::EMPTY },
180+
ty::Generator(..) => {
181+
NaiveLayout { exact: false, niches: NaiveNiches::Maybe, ..NaiveLayout::EMPTY }
182+
}
170183

171184
ty::Closure(_, ref substs) => {
172185
univariant(&mut substs.as_closure().upvar_tys(), &ReprOptions::default())?
@@ -175,14 +188,20 @@ fn naive_layout_of_uncached<'tcx>(
175188
ty::Tuple(tys) => univariant(&mut tys.iter(), &ReprOptions::default())?,
176189

177190
ty::Adt(def, substs) if def.is_union() => {
191+
assert_eq!(def.variants().len(), 1, "union should have a single variant");
178192
let repr = def.repr();
179193
let pack = repr.pack.unwrap_or(Align::MAX);
180194
if repr.pack.is_some() && repr.align.is_some() {
181195
cx.tcx.sess.delay_span_bug(DUMMY_SP, "union cannot be packed and aligned");
182196
return Err(error(cx, LayoutError::Unknown(ty)));
183197
}
184198

185-
let mut layout = NaiveLayout::EMPTY;
199+
let mut layout = NaiveLayout {
200+
// Unions never have niches.
201+
niches: NaiveNiches::None,
202+
..NaiveLayout::EMPTY
203+
};
204+
186205
for f in &def.variants()[FIRST_VARIANT].fields {
187206
let field = cx.naive_layout_of(f.ty(tcx, substs))?;
188207
layout = layout.union(&field.packed(pack));
@@ -201,24 +220,87 @@ fn naive_layout_of_uncached<'tcx>(
201220

202221
ty::Adt(def, substs) => {
203222
let repr = def.repr();
204-
let base = NaiveLayout {
205-
// For simplicity, assume that any enum has its discriminant field (if it exists)
206-
// niched inside one of the variants; this will underestimate the size (and sometimes
207-
// alignment) of enums. We also doesn't compute exact alignment for SIMD structs.
208-
// FIXME(reference_niches): Be smarter here.
209-
// Also consider adding a special case for null-optimized enums, so that we can have
210-
// `Option<&T>: PointerLike` in generic contexts.
211-
exact: !def.is_enum() && !repr.simd(),
223+
let mut layout = NaiveLayout {
212224
// An ADT with no inhabited variants should have an uninhabited ABI.
213225
abi: NaiveAbi::Uninhabited,
214226
..NaiveLayout::EMPTY
215227
};
216228

217-
let layout = def.variants().iter().try_fold(base, |layout, v| {
229+
let mut empty_variants = 0;
230+
for v in def.variants() {
218231
let mut fields = v.fields.iter().map(|f| f.ty(tcx, substs));
219232
let vlayout = univariant(&mut fields, &repr)?;
220-
Ok(layout.union(&vlayout))
221-
})?;
233+
234+
if vlayout.size == Size::ZERO && vlayout.exact {
235+
empty_variants += 1;
236+
} else {
237+
// Remember the niches of the last seen variant.
238+
layout.niches = vlayout.niches;
239+
}
240+
241+
layout = layout.union(&vlayout);
242+
}
243+
244+
if def.is_enum() {
245+
let may_need_discr = match def.variants().len() {
246+
0 | 1 => false,
247+
// Simple Option-like niche optimization.
248+
// Handling this special case allows enums like `Option<&T>`
249+
// to be recognized as `PointerLike` and to be transmutable
250+
// in generic contexts.
251+
2 if empty_variants == 1 && layout.niches == NaiveNiches::Some => {
252+
layout.niches = NaiveNiches::Maybe; // fill up the niche.
253+
false
254+
}
255+
_ => true,
256+
};
257+
258+
if may_need_discr || repr.inhibit_enum_layout_opt() {
259+
// For simplicity, assume that the discriminant always get niched.
260+
// This will be wrong in many cases, which will cause the size (and
261+
// sometimes the alignment) to be underestimated.
262+
// FIXME(reference_niches): Be smarter here.
263+
layout.niches = NaiveNiches::Maybe;
264+
layout = layout.inexact();
265+
}
266+
} else {
267+
assert_eq!(def.variants().len(), 1, "struct should have a single variant");
268+
269+
// We don't compute exact alignment for SIMD structs.
270+
if repr.simd() {
271+
layout = layout.inexact();
272+
}
273+
274+
// `UnsafeCell` hides all niches.
275+
if def.is_unsafe_cell() {
276+
layout.niches = NaiveNiches::None;
277+
}
278+
}
279+
280+
let valid_range = tcx.layout_scalar_valid_range(def.did());
281+
if valid_range != (Bound::Unbounded, Bound::Unbounded) {
282+
let get = |bound, default| match bound {
283+
Bound::Unbounded => default,
284+
Bound::Included(v) => v,
285+
Bound::Excluded(_) => bug!("exclusive `layout_scalar_valid_range` bound"),
286+
};
287+
288+
let valid_range = WrappingRange {
289+
start: get(valid_range.0, 0),
290+
// FIXME: this is wrong for scalar-pair ABIs. Fortunately, the
291+
// only type this could currently affect is`NonNull<T: !Sized>`,
292+
// and the `NaiveNiches` result still ends up correct.
293+
end: get(valid_range.1, layout.size.unsigned_int_max()),
294+
};
295+
assert!(
296+
valid_range.is_in_range_for(layout.size),
297+
"`layout_scalar_valid_range` values are out of bounds",
298+
);
299+
if !valid_range.is_full_for(layout.size) {
300+
layout.niches = NaiveNiches::Some;
301+
}
302+
}
303+
222304
layout.pad_to_align(layout.align)
223305
}
224306

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
error: the compiler unexpectedly panicked. this is a bug.
22

33
query stack during panic:
4-
#0 [layout_of] computing layout of `Foo`
5-
#1 [eval_to_allocation_raw] const-evaluating + checking `FOO`
4+
#0 [naive_layout_of] computing layout (naive) of `Foo`
5+
#1 [layout_of] computing layout of `Foo`
66
end of query stack

0 commit comments

Comments
 (0)