Skip to content

Commit 833dada

Browse files
authored
Rollup merge of rust-lang#62585 - pnkfelix:issue-60431-make-struct-tail-normalize-when-possible, r=eddyb
Make struct_tail normalize when possible As noted in commit message: this replaces the existing methods to extract the struct tail(s) with new entry points that make the handling of normalization explicit. Most of the places that call `struct_tail` are during codegen, post type-checking, and therefore they can get away with using `tcx.normalize_erasing_regions` (this is the entry point `struct_tail_erasing_lifetimes`) For other cases that may arise, one can use the core method, which is parameterized over the normalization `Ty -> Ty` closure (`struct_tail_with_normalize`). Or one can use the trivial entry point that does not normalization (`struct_tail_without_normalization`) ---- I spent a little while trying to make a test that exposed the bug via `impl Trait` rather than a projection, but I failed to find something that tripped up the current nightly `rustc`. * I have *not* spent any time trying to make tests that trip up the other places where `struct_tail` was previously being called. While I do think the task of making such tests could be worthwhile, I am simply running out of time. (Its also possible that the layout code is always the first point called, and thus it may be pointless to try to come up with such tests.) I also spent a little time discussing with @eddyb where this code should live. They suggested moving `struct_tail` and its sibling `struct_lockstep_tails` to the `LayoutCx`. But in the interest of time, I have left that refactoring (which may be questionable at this point) to a follow-up task. ---- Fix rust-lang#60431
2 parents 5e1891c + 3c8279a commit 833dada

File tree

11 files changed

+151
-22
lines changed

11 files changed

+151
-22
lines changed

src/librustc/ty/layout.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -543,7 +543,7 @@ impl<'tcx> LayoutCx<'tcx, TyCtxt<'tcx>> {
543543
return Ok(tcx.intern_layout(LayoutDetails::scalar(self, data_ptr)));
544544
}
545545

546-
let unsized_part = tcx.struct_tail(pointee);
546+
let unsized_part = tcx.struct_tail_erasing_lifetimes(pointee, param_env);
547547
let metadata = match unsized_part.sty {
548548
ty::Foreign(..) => {
549549
return Ok(tcx.intern_layout(LayoutDetails::scalar(self, data_ptr)));
@@ -1664,7 +1664,7 @@ impl<'tcx> SizeSkeleton<'tcx> {
16641664
ty::Ref(_, pointee, _) |
16651665
ty::RawPtr(ty::TypeAndMut { ty: pointee, .. }) => {
16661666
let non_zero = !ty.is_unsafe_ptr();
1667-
let tail = tcx.struct_tail(pointee);
1667+
let tail = tcx.struct_tail_erasing_lifetimes(pointee, param_env);
16681668
match tail.sty {
16691669
ty::Param(_) | ty::Projection(_) => {
16701670
debug_assert!(tail.has_param_types() || tail.has_self_ty());
@@ -2015,7 +2015,7 @@ where
20152015
}));
20162016
}
20172017

2018-
match tcx.struct_tail(pointee).sty {
2018+
match tcx.struct_tail_erasing_lifetimes(pointee, cx.param_env()).sty {
20192019
ty::Slice(_) |
20202020
ty::Str => tcx.types.usize,
20212021
ty::Dynamic(_, _) => {

src/librustc/ty/util.rs

+94-8
Original file line numberDiff line numberDiff line change
@@ -257,10 +257,46 @@ impl<'tcx> TyCtxt<'tcx> {
257257
false
258258
}
259259

260-
/// Returns the deeply last field of nested structures, or the same type,
261-
/// if not a structure at all. Corresponds to the only possible unsized
262-
/// field, and its type can be used to determine unsizing strategy.
263-
pub fn struct_tail(self, mut ty: Ty<'tcx>) -> Ty<'tcx> {
260+
/// Attempts to returns the deeply last field of nested structures, but
261+
/// does not apply any normalization in its search. Returns the same type
262+
/// if input `ty` is not a structure at all.
263+
pub fn struct_tail_without_normalization(self, ty: Ty<'tcx>) -> Ty<'tcx>
264+
{
265+
let tcx = self;
266+
tcx.struct_tail_with_normalize(ty, |ty| ty)
267+
}
268+
269+
/// Returns the deeply last field of nested structures, or the same type if
270+
/// not a structure at all. Corresponds to the only possible unsized field,
271+
/// and its type can be used to determine unsizing strategy.
272+
///
273+
/// Should only be called if `ty` has no inference variables and does not
274+
/// need its lifetimes preserved (e.g. as part of codegen); otherwise
275+
/// normalization attempt may cause compiler bugs.
276+
pub fn struct_tail_erasing_lifetimes(self,
277+
ty: Ty<'tcx>,
278+
param_env: ty::ParamEnv<'tcx>)
279+
-> Ty<'tcx>
280+
{
281+
let tcx = self;
282+
tcx.struct_tail_with_normalize(ty, |ty| tcx.normalize_erasing_regions(param_env, ty))
283+
}
284+
285+
/// Returns the deeply last field of nested structures, or the same type if
286+
/// not a structure at all. Corresponds to the only possible unsized field,
287+
/// and its type can be used to determine unsizing strategy.
288+
///
289+
/// This is parameterized over the normalization strategy (i.e. how to
290+
/// handle `<T as Trait>::Assoc` and `impl Trait`); pass the identity
291+
/// function to indicate no normalization should take place.
292+
///
293+
/// See also `struct_tail_erasing_lifetimes`, which is suitable for use
294+
/// during codegen.
295+
pub fn struct_tail_with_normalize(self,
296+
mut ty: Ty<'tcx>,
297+
normalize: impl Fn(Ty<'tcx>) -> Ty<'tcx>)
298+
-> Ty<'tcx>
299+
{
264300
loop {
265301
match ty.sty {
266302
ty::Adt(def, substs) => {
@@ -281,6 +317,15 @@ impl<'tcx> TyCtxt<'tcx> {
281317
}
282318
}
283319

320+
ty::Projection(_) | ty::Opaque(..) => {
321+
let normalized = normalize(ty);
322+
if ty == normalized {
323+
return ty;
324+
} else {
325+
ty = normalized;
326+
}
327+
}
328+
284329
_ => {
285330
break;
286331
}
@@ -294,10 +339,35 @@ impl<'tcx> TyCtxt<'tcx> {
294339
/// structure definitions.
295340
/// For `(Foo<Foo<T>>, Foo<dyn Trait>)`, the result will be `(Foo<T>, Trait)`,
296341
/// whereas struct_tail produces `T`, and `Trait`, respectively.
297-
pub fn struct_lockstep_tails(self,
298-
source: Ty<'tcx>,
299-
target: Ty<'tcx>)
300-
-> (Ty<'tcx>, Ty<'tcx>) {
342+
///
343+
/// Should only be called if the types have no inference variables and do
344+
/// not need their lifetimes preserved (e.g. as part of codegen); otherwise
345+
/// normalization attempt may cause compiler bugs.
346+
pub fn struct_lockstep_tails_erasing_lifetimes(self,
347+
source: Ty<'tcx>,
348+
target: Ty<'tcx>,
349+
param_env: ty::ParamEnv<'tcx>)
350+
-> (Ty<'tcx>, Ty<'tcx>)
351+
{
352+
let tcx = self;
353+
tcx.struct_lockstep_tails_with_normalize(
354+
source, target, |ty| tcx.normalize_erasing_regions(param_env, ty))
355+
}
356+
357+
/// Same as applying struct_tail on `source` and `target`, but only
358+
/// keeps going as long as the two types are instances of the same
359+
/// structure definitions.
360+
/// For `(Foo<Foo<T>>, Foo<dyn Trait>)`, the result will be `(Foo<T>, Trait)`,
361+
/// whereas struct_tail produces `T`, and `Trait`, respectively.
362+
///
363+
/// See also `struct_lockstep_tails_erasing_lifetimes`, which is suitable for use
364+
/// during codegen.
365+
pub fn struct_lockstep_tails_with_normalize(self,
366+
source: Ty<'tcx>,
367+
target: Ty<'tcx>,
368+
normalize: impl Fn(Ty<'tcx>) -> Ty<'tcx>)
369+
-> (Ty<'tcx>, Ty<'tcx>)
370+
{
301371
let (mut a, mut b) = (source, target);
302372
loop {
303373
match (&a.sty, &b.sty) {
@@ -319,6 +389,22 @@ impl<'tcx> TyCtxt<'tcx> {
319389
break;
320390
}
321391
},
392+
(ty::Projection(_), _) | (ty::Opaque(..), _) |
393+
(_, ty::Projection(_)) | (_, ty::Opaque(..)) => {
394+
// If either side is a projection, attempt to
395+
// progress via normalization. (Should be safe to
396+
// apply to both sides as normalization is
397+
// idempotent.)
398+
let a_norm = normalize(a);
399+
let b_norm = normalize(b);
400+
if a == a_norm && b == b_norm {
401+
break;
402+
} else {
403+
a = a_norm;
404+
b = b_norm;
405+
}
406+
}
407+
322408
_ => break,
323409
}
324410
}

src/librustc_codegen_ssa/base.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,8 @@ pub fn unsized_info<'tcx, Cx: CodegenMethods<'tcx>>(
128128
target: Ty<'tcx>,
129129
old_info: Option<Cx::Value>,
130130
) -> Cx::Value {
131-
let (source, target) = cx.tcx().struct_lockstep_tails(source, target);
131+
let (source, target) =
132+
cx.tcx().struct_lockstep_tails_erasing_lifetimes(source, target, cx.param_env());
132133
match (&source.sty, &target.sty) {
133134
(&ty::Array(_, len), &ty::Slice(_)) => {
134135
cx.const_usize(len.unwrap_usize(cx.tcx()))

src/librustc_codegen_ssa/traits/type_.rs

+3-2
Original file line numberDiff line numberDiff line change
@@ -77,11 +77,12 @@ pub trait DerivedTypeMethods<'tcx>: BaseTypeMethods<'tcx> + MiscMethods<'tcx> {
7777
}
7878

7979
fn type_has_metadata(&self, ty: Ty<'tcx>) -> bool {
80-
if ty.is_sized(self.tcx().at(DUMMY_SP), ty::ParamEnv::reveal_all()) {
80+
let param_env = ty::ParamEnv::reveal_all();
81+
if ty.is_sized(self.tcx().at(DUMMY_SP), param_env) {
8182
return false;
8283
}
8384

84-
let tail = self.tcx().struct_tail(ty);
85+
let tail = self.tcx().struct_tail_erasing_lifetimes(ty, param_env);
8586
match tail.sty {
8687
ty::Foreign(..) => false,
8788
ty::Str | ty::Slice(..) | ty::Dynamic(..) => true,

src/librustc_mir/interpret/cast.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -270,7 +270,8 @@ impl<'mir, 'tcx, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
270270
dty: Ty<'tcx>,
271271
) -> InterpResult<'tcx> {
272272
// A<Struct> -> A<Trait> conversion
273-
let (src_pointee_ty, dest_pointee_ty) = self.tcx.struct_lockstep_tails(sty, dty);
273+
let (src_pointee_ty, dest_pointee_ty) =
274+
self.tcx.struct_lockstep_tails_erasing_lifetimes(sty, dty, self.param_env);
274275

275276
match (&src_pointee_ty.sty, &dest_pointee_ty.sty) {
276277
(&ty::Array(_, length), &ty::Slice(_)) => {

src/librustc_mir/interpret/intern.rs

+3-1
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,9 @@ for
146146
let value = self.ecx.read_immediate(mplace.into())?;
147147
// Handle trait object vtables
148148
if let Ok(meta) = value.to_meta() {
149-
if let ty::Dynamic(..) = self.ecx.tcx.struct_tail(referenced_ty).sty {
149+
if let ty::Dynamic(..) =
150+
self.ecx.tcx.struct_tail_erasing_lifetimes(referenced_ty, self.param_env).sty
151+
{
150152
if let Ok(vtable) = meta.unwrap().to_ptr() {
151153
// explitly choose `Immutable` here, since vtables are immutable, even
152154
// if the reference of the fat pointer is mutable

src/librustc_mir/interpret/validity.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -361,7 +361,8 @@ impl<'rt, 'mir, 'tcx, M: Machine<'mir, 'tcx>> ValueVisitor<'mir, 'tcx, M>
361361
"uninitialized data in fat pointer metadata", self.path);
362362
let layout = self.ecx.layout_of(value.layout.ty.builtin_deref(true).unwrap().ty)?;
363363
if layout.is_unsized() {
364-
let tail = self.ecx.tcx.struct_tail(layout.ty);
364+
let tail = self.ecx.tcx.struct_tail_erasing_lifetimes(layout.ty,
365+
self.ecx.param_env);
365366
match tail.sty {
366367
ty::Dynamic(..) => {
367368
let vtable = meta.unwrap();

src/librustc_mir/monomorphize/collector.rs

+4-3
Original file line numberDiff line numberDiff line change
@@ -851,12 +851,13 @@ fn find_vtable_types_for_unsizing<'tcx>(
851851
target_ty: Ty<'tcx>,
852852
) -> (Ty<'tcx>, Ty<'tcx>) {
853853
let ptr_vtable = |inner_source: Ty<'tcx>, inner_target: Ty<'tcx>| {
854+
let param_env = ty::ParamEnv::reveal_all();
854855
let type_has_metadata = |ty: Ty<'tcx>| -> bool {
855856
use syntax_pos::DUMMY_SP;
856-
if ty.is_sized(tcx.at(DUMMY_SP), ty::ParamEnv::reveal_all()) {
857+
if ty.is_sized(tcx.at(DUMMY_SP), param_env) {
857858
return false;
858859
}
859-
let tail = tcx.struct_tail(ty);
860+
let tail = tcx.struct_tail_erasing_lifetimes(ty, param_env);
860861
match tail.sty {
861862
ty::Foreign(..) => false,
862863
ty::Str | ty::Slice(..) | ty::Dynamic(..) => true,
@@ -866,7 +867,7 @@ fn find_vtable_types_for_unsizing<'tcx>(
866867
if type_has_metadata(inner_source) {
867868
(inner_source, inner_target)
868869
} else {
869-
tcx.struct_lockstep_tails(inner_source, inner_target)
870+
tcx.struct_lockstep_tails_erasing_lifetimes(inner_source, inner_target, param_env)
870871
}
871872
};
872873

src/librustc_typeck/check/mod.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -315,7 +315,7 @@ impl<'a, 'tcx> Expectation<'tcx> {
315315
/// See the test case `test/run-pass/coerce-expect-unsized.rs` and #20169
316316
/// for examples of where this comes up,.
317317
fn rvalue_hint(fcx: &FnCtxt<'a, 'tcx>, ty: Ty<'tcx>) -> Expectation<'tcx> {
318-
match fcx.tcx.struct_tail(ty).sty {
318+
match fcx.tcx.struct_tail_without_normalization(ty).sty {
319319
ty::Slice(_) | ty::Str | ty::Dynamic(..) => {
320320
ExpectRvalueLikeUnsized(ty)
321321
}

src/librustc_typeck/check/wfcheck.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -366,7 +366,8 @@ fn check_item_type(
366366

367367
let mut forbid_unsized = true;
368368
if allow_foreign_ty {
369-
if let ty::Foreign(_) = fcx.tcx.struct_tail(item_ty).sty {
369+
let tail = fcx.tcx.struct_tail_erasing_lifetimes(item_ty, fcx.param_env);
370+
if let ty::Foreign(_) = tail.sty {
370371
forbid_unsized = false;
371372
}
372373
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
// rust-lang/rust#60431: This is a scenario where to determine the size of
2+
// `&Ref<Obstack>`, we need to know the concrete type of the last field in
3+
// `Ref<Obstack>` (i.e. its "struct tail"), and determining that concrete type
4+
// requires normalizing `Obstack::Dyn`.
5+
//
6+
// The old "struct tail" computation did not perform such normalization, and so
7+
// the compiler would ICE when trying to figure out if `Ref<Obstack>` is a
8+
// dynamically-sized type (DST).
9+
10+
// run-pass
11+
12+
use std::mem;
13+
14+
pub trait Arena {
15+
type Dyn : ?Sized;
16+
}
17+
18+
pub struct DynRef {
19+
_dummy: [()],
20+
}
21+
22+
pub struct Ref<A: Arena> {
23+
_value: u8,
24+
_dyn_arena: A::Dyn,
25+
}
26+
27+
pub struct Obstack;
28+
29+
impl Arena for Obstack {
30+
type Dyn = DynRef;
31+
}
32+
33+
fn main() {
34+
assert_eq!(mem::size_of::<&Ref<Obstack>>(), mem::size_of::<&[()]>());
35+
}

0 commit comments

Comments
 (0)