Skip to content

Commit c30fbb9

Browse files
committed
Track exactness in NaiveLayout and use it for SizeSkeleton checks
1 parent 403f34b commit c30fbb9

File tree

4 files changed

+70
-44
lines changed

4 files changed

+70
-44
lines changed

compiler/rustc_middle/src/ty/layout.rs

Lines changed: 29 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ use rustc_target::abi::call::FnAbi;
1515
use rustc_target::abi::*;
1616
use rustc_target::spec::{abi::Abi as SpecAbi, HasTargetSpec, PanicStrategy, Target};
1717

18-
use std::cmp;
18+
use std::cmp::{self, Ordering};
1919
use std::fmt;
2020
use std::num::NonZeroUsize;
2121
use std::ops::Bound;
@@ -313,7 +313,16 @@ impl<'tcx> SizeSkeleton<'tcx> {
313313
) -> Result<SizeSkeleton<'tcx>, &'tcx LayoutError<'tcx>> {
314314
debug_assert!(!ty.has_non_region_infer());
315315

316-
// First try computing a static layout.
316+
// First, try computing an exact naive layout (this covers simple types with generic
317+
// references, where a full static layout would fail).
318+
if let Ok(layout) = tcx.naive_layout_of(param_env.and(ty)) {
319+
if layout.is_exact {
320+
return Ok(SizeSkeleton::Known(layout.min_size));
321+
}
322+
}
323+
324+
// Second, try computing a full static layout (this covers cases when the naive layout
325+
// wasn't smart enough, but cannot deal with generic references).
317326
let err = match tcx.layout_of(param_env.and(ty)) {
318327
Ok(layout) => {
319328
return Ok(SizeSkeleton::Known(layout.size));
@@ -327,6 +336,7 @@ impl<'tcx> SizeSkeleton<'tcx> {
327336
) => return Err(e),
328337
};
329338

339+
// Third, fall back to ad-hoc cases.
330340
match *ty.kind() {
331341
ty::Ref(_, pointee, _) | ty::RawPtr(ty::TypeAndMut { ty: pointee, .. }) => {
332342
let non_zero = !ty.is_unsafe_ptr();
@@ -645,25 +655,36 @@ impl std::ops::DerefMut for TyAndNaiveLayout<'_> {
645655
pub struct NaiveLayout {
646656
pub min_size: Size,
647657
pub min_align: Align,
658+
// If `true`, `min_size` and `min_align` are guaranteed to be exact.
659+
pub is_exact: bool,
648660
}
649661

650662
impl NaiveLayout {
651-
pub const EMPTY: Self = Self { min_size: Size::ZERO, min_align: Align::ONE };
663+
pub const UNKNOWN: Self = Self { min_size: Size::ZERO, min_align: Align::ONE, is_exact: false };
664+
pub const EMPTY: Self = Self { min_size: Size::ZERO, min_align: Align::ONE, is_exact: true };
665+
666+
pub fn is_compatible_with(&self, layout: Layout<'_>) -> bool {
667+
let cmp = |cmp: Ordering| match (cmp, self.is_exact) {
668+
(Ordering::Less | Ordering::Equal, false) => true,
669+
(Ordering::Equal, true) => true,
670+
(_, _) => false,
671+
};
652672

653-
pub fn is_underestimate_of(&self, layout: Layout<'_>) -> bool {
654-
self.min_size <= layout.size() && self.min_align <= layout.align().abi
673+
cmp(self.min_size.cmp(&layout.size())) && cmp(self.min_align.cmp(&layout.align().abi))
655674
}
656675

657676
#[must_use]
658-
pub fn pad_to_align(self) -> Self {
659-
Self { min_size: self.min_size.align_to(self.min_align), min_align: self.min_align }
677+
pub fn pad_to_align(mut self) -> Self {
678+
self.min_size = self.min_size.align_to(self.min_align);
679+
self
660680
}
661681

662682
#[must_use]
663683
pub fn concat<C: HasDataLayout>(&self, other: &Self, cx: &C) -> Option<Self> {
664684
Some(Self {
665685
min_size: self.min_size.checked_add(other.min_size, cx)?,
666686
min_align: std::cmp::max(self.min_align, other.min_align),
687+
is_exact: self.is_exact && other.is_exact,
667688
})
668689
}
669690

@@ -672,6 +693,7 @@ impl NaiveLayout {
672693
Self {
673694
min_size: std::cmp::max(self.min_size, other.min_size),
674695
min_align: std::cmp::max(self.min_align, other.min_align),
696+
is_exact: self.is_exact && other.is_exact,
675697
}
676698
}
677699
}

compiler/rustc_ty_utils/src/layout.rs

Lines changed: 39 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -90,9 +90,9 @@ fn layout_of<'tcx>(
9090
let cx = LayoutCx { tcx, param_env };
9191
let layout = layout_of_uncached(&cx, ty)?;
9292

93-
if !naive.is_underestimate_of(layout) {
93+
if !naive.is_compatible_with(layout) {
9494
bug!(
95-
"the estimated naive layout is bigger than the actual layout:\n{:#?}\n{:#?}",
95+
"the naive layout isn't compatible with the actual layout:\n{:#?}\n{:#?}",
9696
naive,
9797
layout,
9898
);
@@ -119,15 +119,23 @@ fn naive_layout_of_uncached<'tcx>(
119119
let tcx = cx.tcx;
120120
let dl = cx.data_layout();
121121

122-
let scalar =
123-
|value: Primitive| NaiveLayout { min_size: value.size(dl), min_align: value.align(dl).abi };
122+
let scalar = |value: Primitive| NaiveLayout {
123+
min_size: value.size(dl),
124+
min_align: value.align(dl).abi,
125+
is_exact: true,
126+
};
124127

125128
let univariant = |fields: &mut dyn Iterator<Item = Ty<'tcx>>,
126129
repr: &ReprOptions|
127130
-> Result<NaiveLayout, &'tcx LayoutError<'tcx>> {
131+
if repr.pack.is_some() && repr.align.is_some() {
132+
cx.tcx.sess.delay_span_bug(DUMMY_SP, "struct cannot be packed and aligned");
133+
return Err(error(cx, LayoutError::Unknown(ty)));
134+
}
135+
128136
// For simplicity, ignore inter-field padding; this may underestimate the size.
129137
// FIXME(reference_niches): Be smarter and implement something closer to the real layout logic.
130-
let mut layout = NaiveLayout::EMPTY;
138+
let mut layout = NaiveLayout::UNKNOWN;
131139
for field in fields {
132140
let field = cx.naive_layout_of(field)?;
133141
layout = layout
@@ -192,20 +200,22 @@ fn naive_layout_of_uncached<'tcx>(
192200
.min_size
193201
.checked_mul(count, cx)
194202
.ok_or_else(|| error(cx, LayoutError::SizeOverflow(ty)))?,
195-
min_align: element.min_align,
203+
..*element
196204
}
197205
}
198-
ty::Slice(element) => {
199-
NaiveLayout { min_size: Size::ZERO, min_align: cx.naive_layout_of(element)?.min_align }
200-
}
206+
ty::Slice(element) => NaiveLayout {
207+
min_size: Size::ZERO,
208+
// NOTE: this could be unconditionally exact if `NaiveLayout` guaranteed exact align.
209+
..*cx.naive_layout_of(element)?
210+
},
201211
ty::Str => NaiveLayout::EMPTY,
202212

203213
// Odd unit types.
204214
ty::FnDef(..) | ty::Dynamic(_, _, ty::Dyn) | ty::Foreign(..) => NaiveLayout::EMPTY,
205215

206216
// FIXME(reference_niches): try to actually compute a reasonable layout estimate,
207217
// without duplicating too much code from `generator_layout`.
208-
ty::Generator(..) => NaiveLayout::EMPTY,
218+
ty::Generator(..) => NaiveLayout::UNKNOWN,
209219

210220
ty::Closure(_, ref substs) => {
211221
univariant(&mut substs.as_closure().upvar_tys(), &ReprOptions::default())?
@@ -223,12 +233,21 @@ fn naive_layout_of_uncached<'tcx>(
223233
}
224234

225235
ty::Adt(def, substs) => {
226-
// For simplicity, assume that any discriminant field (if it exists)
227-
// gets niched inside one of the variants; this will underestimate the size
228-
// (and sometimes alignment) of enums.
229-
// FIXME(reference_niches): Be smarter and actually take into accoount the discriminant.
230236
let repr = def.repr();
231-
def.variants().iter().try_fold(NaiveLayout::EMPTY, |layout, v| {
237+
let base = if def.is_struct() && !repr.simd() {
238+
// FIXME(reference_niches): compute proper alignment for SIMD types.
239+
NaiveLayout::EMPTY
240+
} else {
241+
// For simplicity, assume that any discriminant field (if it exists)
242+
// gets niched inside one of the variants; this will underestimate the size
243+
// (and sometimes alignment) of enums.
244+
// FIXME(reference_niches): Be smarter and actually take into accoount the discriminant.
245+
// Also consider adding a special case for null-optimized enums, so that we can have
246+
// `Option<&T>: PointerLike` in generic contexts.
247+
NaiveLayout::UNKNOWN
248+
};
249+
250+
def.variants().iter().try_fold(base, |layout, v| {
232251
let mut fields = v.fields.iter().map(|f| f.ty(tcx, substs));
233252
let vlayout = univariant(&mut fields, &repr)?;
234253
Ok(layout.union(&vlayout))
@@ -260,12 +279,10 @@ fn univariant_uninterned<'tcx>(
260279
kind: StructKind,
261280
) -> Result<LayoutS, &'tcx LayoutError<'tcx>> {
262281
let dl = cx.data_layout();
263-
let pack = repr.pack;
264-
if pack.is_some() && repr.align.is_some() {
265-
cx.tcx.sess.delay_span_bug(DUMMY_SP, "struct cannot be packed and aligned");
266-
return Err(cx.tcx.arena.alloc(LayoutError::Unknown(ty)));
267-
}
268-
282+
assert!(
283+
!(repr.pack.is_some() && repr.align.is_some()),
284+
"already rejected by `naive_layout_of`"
285+
);
269286
cx.univariant(dl, fields, repr, kind).ok_or_else(|| error(cx, LayoutError::SizeOverflow(ty)))
270287
}
271288

@@ -325,17 +342,7 @@ fn layout_of_uncached<'tcx>(
325342
if !ty.is_unsafe_ptr() {
326343
// Calling `layout_of` here would cause a query cycle for recursive types;
327344
// so use a conservative estimate that doesn't look past references.
328-
let naive = match cx.naive_layout_of(pointee) {
329-
Ok(n) => n.layout,
330-
// This can happen when computing the `SizeSkeleton` of a generic type.
331-
Err(LayoutError::Unknown(_)) => {
332-
// TODO(reference_niches): this is *very* incorrect, but we can't
333-
// return an error here; this would break transmute checks.
334-
// We need some other solution.
335-
NaiveLayout::EMPTY
336-
}
337-
Err(err) => return Err(err),
338-
};
345+
let naive = cx.naive_layout_of(pointee)?.layout;
339346

340347
let niches = match *pointee.kind() {
341348
ty::FnDef(def, ..)

tests/ui/lint/invalid_value.stderr

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,7 @@ LL | let _val: Wrap<&'static T> = mem::zeroed();
3434
| this code causes undefined behavior when executed
3535
| help: use `MaybeUninit<T>` instead, and only call `assume_init` after initialization is done
3636
|
37-
= note: `Wrap<&T>` must be non-null
38-
note: because references must be non-null (in this struct field)
37+
note: references must be non-null (in this struct field)
3938
--> $DIR/invalid_value.rs:17:18
4039
|
4140
LL | struct Wrap<T> { wrapped: T }
@@ -50,8 +49,7 @@ LL | let _val: Wrap<&'static T> = mem::uninitialized();
5049
| this code causes undefined behavior when executed
5150
| help: use `MaybeUninit<T>` instead, and only call `assume_init` after initialization is done
5251
|
53-
= note: `Wrap<&T>` must be non-null
54-
note: because references must be non-null (in this struct field)
52+
note: references must be non-null (in this struct field)
5553
--> $DIR/invalid_value.rs:17:18
5654
|
5755
LL | struct Wrap<T> { wrapped: T }

tests/ui/type-alias-impl-trait/issue-53092-2.stderr

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ note: ...which requires type-checking `CONST_BUG`...
99
|
1010
LL | const CONST_BUG: Bug<u8, ()> = unsafe { std::mem::transmute(|_: u8| ()) };
1111
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
12-
= note: ...which requires computing layout of `Bug<u8, ()>`...
1312
= note: ...which requires computing layout (naive) of `Bug<u8, ()>`...
1413
= note: ...which requires normalizing `Bug<u8, ()>`...
1514
= note: ...which again requires computing type of `Bug::{opaque#0}`, completing the cycle

0 commit comments

Comments
 (0)