Skip to content

Commit c6bc102

Browse files
committed
Auto merge of rust-lang#87515 - crlf0710:trait_upcasting_part2, r=bjorn3
Trait upcasting coercion (part2) This is the second part of trait upcasting coercion implementation. Currently this is blocked on rust-lang#86264 . The third part might be implemented using unsafety checking r? `@bjorn3`
2 parents d5fd37f + 3cb625e commit c6bc102

File tree

22 files changed

+1186
-93
lines changed

22 files changed

+1186
-93
lines changed

compiler/rustc_codegen_cranelift/src/unsize.rs

+48-15
Original file line numberDiff line numberDiff line change
@@ -25,39 +25,73 @@ pub(crate) fn unsized_info<'tcx>(
2525
.bcx
2626
.ins()
2727
.iconst(fx.pointer_type, len.eval_usize(fx.tcx, ParamEnv::reveal_all()) as i64),
28-
(&ty::Dynamic(..), &ty::Dynamic(..)) => {
29-
// For now, upcasts are limited to changes in marker
30-
// traits, and hence never actually require an actual
31-
// change to the vtable.
32-
old_info.expect("unsized_info: missing old info for trait upcast")
28+
(&ty::Dynamic(ref data_a, ..), &ty::Dynamic(ref data_b, ..)) => {
29+
let old_info =
30+
old_info.expect("unsized_info: missing old info for trait upcasting coercion");
31+
if data_a.principal_def_id() == data_b.principal_def_id() {
32+
return old_info;
33+
}
34+
// trait upcasting coercion
35+
36+
// if both of the two `principal`s are `None`, this function would have returned early above.
37+
// and if one of the two `principal`s is `None`, typechecking would have rejected this case.
38+
let principal_a = data_a
39+
.principal()
40+
.expect("unsized_info: missing principal trait for trait upcasting coercion");
41+
let principal_b = data_b
42+
.principal()
43+
.expect("unsized_info: missing principal trait for trait upcasting coercion");
44+
45+
let vptr_entry_idx = fx.tcx.vtable_trait_upcasting_coercion_new_vptr_slot((
46+
principal_a.with_self_ty(fx.tcx, source),
47+
principal_b.with_self_ty(fx.tcx, source),
48+
));
49+
50+
if let Some(entry_idx) = vptr_entry_idx {
51+
let entry_idx = u32::try_from(entry_idx).unwrap();
52+
let entry_offset = entry_idx * fx.pointer_type.bytes();
53+
let vptr_ptr = Pointer::new(old_info).offset_i64(fx, entry_offset.into()).load(
54+
fx,
55+
fx.pointer_type,
56+
crate::vtable::vtable_memflags(),
57+
);
58+
vptr_ptr
59+
} else {
60+
old_info
61+
}
3362
}
3463
(_, &ty::Dynamic(ref data, ..)) => crate::vtable::get_vtable(fx, source, data.principal()),
3564
_ => bug!("unsized_info: invalid unsizing {:?} -> {:?}", source, target),
3665
}
3766
}
3867

39-
/// Coerce `src` to `dst_ty`. `src_ty` must be a thin pointer.
40-
fn unsize_thin_ptr<'tcx>(
68+
/// Coerce `src` to `dst_ty`.
69+
fn unsize_ptr<'tcx>(
4170
fx: &mut FunctionCx<'_, '_, 'tcx>,
4271
src: Value,
4372
src_layout: TyAndLayout<'tcx>,
4473
dst_layout: TyAndLayout<'tcx>,
74+
old_info: Option<Value>,
4575
) -> (Value, Value) {
4676
match (&src_layout.ty.kind(), &dst_layout.ty.kind()) {
4777
(&ty::Ref(_, a, _), &ty::Ref(_, b, _))
4878
| (&ty::Ref(_, a, _), &ty::RawPtr(ty::TypeAndMut { ty: b, .. }))
4979
| (&ty::RawPtr(ty::TypeAndMut { ty: a, .. }), &ty::RawPtr(ty::TypeAndMut { ty: b, .. })) => {
5080
assert!(!fx.layout_of(a).is_unsized());
51-
(src, unsized_info(fx, a, b, None))
81+
(src, unsized_info(fx, a, b, old_info))
5282
}
5383
(&ty::Adt(def_a, _), &ty::Adt(def_b, _)) if def_a.is_box() && def_b.is_box() => {
5484
let (a, b) = (src_layout.ty.boxed_ty(), dst_layout.ty.boxed_ty());
5585
assert!(!fx.layout_of(a).is_unsized());
56-
(src, unsized_info(fx, a, b, None))
86+
(src, unsized_info(fx, a, b, old_info))
5787
}
5888
(&ty::Adt(def_a, _), &ty::Adt(def_b, _)) => {
5989
assert_eq!(def_a, def_b);
6090

91+
if src_layout == dst_layout {
92+
return (src, old_info.unwrap());
93+
}
94+
6195
let mut result = None;
6296
for i in 0..src_layout.fields.count() {
6397
let src_f = src_layout.field(fx, i);
@@ -71,11 +105,11 @@ fn unsize_thin_ptr<'tcx>(
71105
let dst_f = dst_layout.field(fx, i);
72106
assert_ne!(src_f.ty, dst_f.ty);
73107
assert_eq!(result, None);
74-
result = Some(unsize_thin_ptr(fx, src, src_f, dst_f));
108+
result = Some(unsize_ptr(fx, src, src_f, dst_f, old_info));
75109
}
76110
result.unwrap()
77111
}
78-
_ => bug!("unsize_thin_ptr: called on bad types"),
112+
_ => bug!("unsize_ptr: called on bad types"),
79113
}
80114
}
81115

@@ -91,12 +125,11 @@ pub(crate) fn coerce_unsized_into<'tcx>(
91125
let mut coerce_ptr = || {
92126
let (base, info) =
93127
if fx.layout_of(src.layout().ty.builtin_deref(true).unwrap().ty).is_unsized() {
94-
// fat-ptr to fat-ptr unsize preserves the vtable
95-
// i.e., &'a fmt::Debug+Send => &'a fmt::Debug
96-
src.load_scalar_pair(fx)
128+
let (old_base, old_info) = src.load_scalar_pair(fx);
129+
unsize_ptr(fx, old_base, src.layout(), dst.layout(), Some(old_info))
97130
} else {
98131
let base = src.load_scalar(fx);
99-
unsize_thin_ptr(fx, base, src.layout(), dst.layout())
132+
unsize_ptr(fx, base, src.layout(), dst.layout(), None)
100133
};
101134
dst.write_cvalue(fx, CValue::by_val_pair(base, info, dst.layout()));
102135
};

compiler/rustc_codegen_cranelift/src/vtable.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
use crate::constant::data_id_for_alloc_id;
66
use crate::prelude::*;
77

8-
fn vtable_memflags() -> MemFlags {
8+
pub(crate) fn vtable_memflags() -> MemFlags {
99
let mut flags = MemFlags::trusted(); // A vtable access is always aligned and will never trap.
1010
flags.set_readonly(); // A vtable is always read-only.
1111
flags

compiler/rustc_codegen_ssa/src/base.rs

+66-41
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@ use rustc_middle::middle::cstore::EncodedMetadata;
2323
use rustc_middle::middle::lang_items;
2424
use rustc_middle::mir::mono::{CodegenUnit, CodegenUnitNameBuilder, MonoItem};
2525
use rustc_middle::ty::layout::{HasTyCtxt, TyAndLayout};
26-
use rustc_middle::ty::layout::{FAT_PTR_ADDR, FAT_PTR_EXTRA};
2726
use rustc_middle::ty::query::Providers;
2827
use rustc_middle::ty::{self, Instance, Ty, TyCtxt};
2928
use rustc_session::cgu_reuse_tracker::CguReuse;
@@ -32,6 +31,7 @@ use rustc_session::Session;
3231
use rustc_span::symbol::sym;
3332
use rustc_target::abi::{Align, LayoutOf, VariantIdx};
3433

34+
use std::convert::TryFrom;
3535
use std::ops::{Deref, DerefMut};
3636
use std::time::{Duration, Instant};
3737

@@ -128,55 +128,92 @@ pub fn compare_simd_types<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>(
128128
///
129129
/// The `old_info` argument is a bit odd. It is intended for use in an upcast,
130130
/// where the new vtable for an object will be derived from the old one.
131-
pub fn unsized_info<'tcx, Cx: CodegenMethods<'tcx>>(
132-
cx: &Cx,
131+
pub fn unsized_info<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>(
132+
bx: &mut Bx,
133133
source: Ty<'tcx>,
134134
target: Ty<'tcx>,
135-
old_info: Option<Cx::Value>,
136-
) -> Cx::Value {
135+
old_info: Option<Bx::Value>,
136+
) -> Bx::Value {
137+
let cx = bx.cx();
137138
let (source, target) =
138-
cx.tcx().struct_lockstep_tails_erasing_lifetimes(source, target, cx.param_env());
139+
cx.tcx().struct_lockstep_tails_erasing_lifetimes(source, target, bx.param_env());
139140
match (source.kind(), target.kind()) {
140141
(&ty::Array(_, len), &ty::Slice(_)) => {
141142
cx.const_usize(len.eval_usize(cx.tcx(), ty::ParamEnv::reveal_all()))
142143
}
143-
(&ty::Dynamic(..), &ty::Dynamic(..)) => {
144-
// For now, upcasts are limited to changes in marker
145-
// traits, and hence never actually require an actual
146-
// change to the vtable.
147-
old_info.expect("unsized_info: missing old info for trait upcast")
144+
(&ty::Dynamic(ref data_a, ..), &ty::Dynamic(ref data_b, ..)) => {
145+
let old_info =
146+
old_info.expect("unsized_info: missing old info for trait upcasting coercion");
147+
if data_a.principal_def_id() == data_b.principal_def_id() {
148+
return old_info;
149+
}
150+
151+
// trait upcasting coercion
152+
153+
// if both of the two `principal`s are `None`, this function would have returned early above.
154+
// and if one of the two `principal`s is `None`, typechecking would have rejected this case.
155+
let principal_a = data_a
156+
.principal()
157+
.expect("unsized_info: missing principal trait for trait upcasting coercion");
158+
let principal_b = data_b
159+
.principal()
160+
.expect("unsized_info: missing principal trait for trait upcasting coercion");
161+
162+
let vptr_entry_idx = cx.tcx().vtable_trait_upcasting_coercion_new_vptr_slot((
163+
principal_a.with_self_ty(cx.tcx(), source),
164+
principal_b.with_self_ty(cx.tcx(), source),
165+
));
166+
167+
if let Some(entry_idx) = vptr_entry_idx {
168+
let ptr_ty = cx.type_i8p();
169+
let ptr_align = cx.tcx().data_layout.pointer_align.abi;
170+
let llvtable = bx.pointercast(old_info, bx.type_ptr_to(ptr_ty));
171+
let gep =
172+
bx.inbounds_gep(llvtable, &[bx.const_usize(u64::try_from(entry_idx).unwrap())]);
173+
let new_vptr = bx.load(ptr_ty, gep, ptr_align);
174+
bx.nonnull_metadata(new_vptr);
175+
// Vtable loads are invariant.
176+
bx.set_invariant_load(new_vptr);
177+
new_vptr
178+
} else {
179+
old_info
180+
}
148181
}
149182
(_, &ty::Dynamic(ref data, ..)) => {
150-
let vtable_ptr = cx.layout_of(cx.tcx().mk_mut_ptr(target)).field(cx, FAT_PTR_EXTRA);
151-
cx.const_ptrcast(
152-
meth::get_vtable(cx, source, data.principal()),
153-
cx.backend_type(vtable_ptr),
154-
)
183+
let vtable_ptr_ty = cx.scalar_pair_element_backend_type(
184+
cx.layout_of(cx.tcx().mk_mut_ptr(target)),
185+
1,
186+
true,
187+
);
188+
cx.const_ptrcast(meth::get_vtable(cx, source, data.principal()), vtable_ptr_ty)
155189
}
156190
_ => bug!("unsized_info: invalid unsizing {:?} -> {:?}", source, target),
157191
}
158192
}
159193

160-
/// Coerces `src` to `dst_ty`. `src_ty` must be a thin pointer.
161-
pub fn unsize_thin_ptr<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>(
194+
/// Coerces `src` to `dst_ty`. `src_ty` must be a pointer.
195+
pub fn unsize_ptr<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>(
162196
bx: &mut Bx,
163197
src: Bx::Value,
164198
src_ty: Ty<'tcx>,
165199
dst_ty: Ty<'tcx>,
200+
old_info: Option<Bx::Value>,
166201
) -> (Bx::Value, Bx::Value) {
167-
debug!("unsize_thin_ptr: {:?} => {:?}", src_ty, dst_ty);
202+
debug!("unsize_ptr: {:?} => {:?}", src_ty, dst_ty);
168203
match (src_ty.kind(), dst_ty.kind()) {
169204
(&ty::Ref(_, a, _), &ty::Ref(_, b, _) | &ty::RawPtr(ty::TypeAndMut { ty: b, .. }))
170205
| (&ty::RawPtr(ty::TypeAndMut { ty: a, .. }), &ty::RawPtr(ty::TypeAndMut { ty: b, .. })) => {
171-
assert!(bx.cx().type_is_sized(a));
206+
assert_eq!(bx.cx().type_is_sized(a), old_info.is_none());
172207
let ptr_ty = bx.cx().type_ptr_to(bx.cx().backend_type(bx.cx().layout_of(b)));
173-
(bx.pointercast(src, ptr_ty), unsized_info(bx.cx(), a, b, None))
208+
(bx.pointercast(src, ptr_ty), unsized_info(bx, a, b, old_info))
174209
}
175210
(&ty::Adt(def_a, _), &ty::Adt(def_b, _)) => {
176211
assert_eq!(def_a, def_b);
177-
178212
let src_layout = bx.cx().layout_of(src_ty);
179213
let dst_layout = bx.cx().layout_of(dst_ty);
214+
if src_ty == dst_ty {
215+
return (src, old_info.unwrap());
216+
}
180217
let mut result = None;
181218
for i in 0..src_layout.fields.count() {
182219
let src_f = src_layout.field(bx.cx(), i);
@@ -190,18 +227,15 @@ pub fn unsize_thin_ptr<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>(
190227
let dst_f = dst_layout.field(bx.cx(), i);
191228
assert_ne!(src_f.ty, dst_f.ty);
192229
assert_eq!(result, None);
193-
result = Some(unsize_thin_ptr(bx, src, src_f.ty, dst_f.ty));
230+
result = Some(unsize_ptr(bx, src, src_f.ty, dst_f.ty, old_info));
194231
}
195232
let (lldata, llextra) = result.unwrap();
233+
let lldata_ty = bx.cx().scalar_pair_element_backend_type(dst_layout, 0, true);
234+
let llextra_ty = bx.cx().scalar_pair_element_backend_type(dst_layout, 1, true);
196235
// HACK(eddyb) have to bitcast pointers until LLVM removes pointee types.
197-
// FIXME(eddyb) move these out of this `match` arm, so they're always
198-
// applied, uniformly, no matter the source/destination types.
199-
(
200-
bx.bitcast(lldata, bx.cx().scalar_pair_element_backend_type(dst_layout, 0, true)),
201-
bx.bitcast(llextra, bx.cx().scalar_pair_element_backend_type(dst_layout, 1, true)),
202-
)
236+
(bx.bitcast(lldata, lldata_ty), bx.bitcast(llextra, llextra_ty))
203237
}
204-
_ => bug!("unsize_thin_ptr: called on bad types"),
238+
_ => bug!("unsize_ptr: called on bad types"),
205239
}
206240
}
207241

@@ -217,17 +251,8 @@ pub fn coerce_unsized_into<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>(
217251
match (src_ty.kind(), dst_ty.kind()) {
218252
(&ty::Ref(..), &ty::Ref(..) | &ty::RawPtr(..)) | (&ty::RawPtr(..), &ty::RawPtr(..)) => {
219253
let (base, info) = match bx.load_operand(src).val {
220-
OperandValue::Pair(base, info) => {
221-
// fat-ptr to fat-ptr unsize preserves the vtable
222-
// i.e., &'a fmt::Debug+Send => &'a fmt::Debug
223-
// So we need to pointercast the base to ensure
224-
// the types match up.
225-
// FIXME(eddyb) use `scalar_pair_element_backend_type` here,
226-
// like `unsize_thin_ptr` does.
227-
let thin_ptr = dst.layout.field(bx.cx(), FAT_PTR_ADDR);
228-
(bx.pointercast(base, bx.cx().backend_type(thin_ptr)), info)
229-
}
230-
OperandValue::Immediate(base) => unsize_thin_ptr(bx, base, src_ty, dst_ty),
254+
OperandValue::Pair(base, info) => unsize_ptr(bx, base, src_ty, dst_ty, Some(info)),
255+
OperandValue::Immediate(base) => unsize_ptr(bx, base, src_ty, dst_ty, None),
231256
OperandValue::Ref(..) => bug!(),
232257
};
233258
OperandValue::Pair(base, info).store(bx, dst);

compiler/rustc_codegen_ssa/src/mir/rvalue.rs

+8-19
Original file line numberDiff line numberDiff line change
@@ -220,34 +220,23 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
220220
}
221221
mir::CastKind::Pointer(PointerCast::Unsize) => {
222222
assert!(bx.cx().is_backend_scalar_pair(cast));
223-
match operand.val {
223+
let (lldata, llextra) = match operand.val {
224224
OperandValue::Pair(lldata, llextra) => {
225225
// unsize from a fat pointer -- this is a
226-
// "trait-object-to-supertrait" coercion, for
227-
// example, `&'a fmt::Debug + Send => &'a fmt::Debug`.
228-
229-
// HACK(eddyb) have to bitcast pointers
230-
// until LLVM removes pointee types.
231-
let lldata = bx.pointercast(
232-
lldata,
233-
bx.cx().scalar_pair_element_backend_type(cast, 0, true),
234-
);
235-
OperandValue::Pair(lldata, llextra)
226+
// "trait-object-to-supertrait" coercion.
227+
(lldata, Some(llextra))
236228
}
237229
OperandValue::Immediate(lldata) => {
238230
// "standard" unsize
239-
let (lldata, llextra) = base::unsize_thin_ptr(
240-
&mut bx,
241-
lldata,
242-
operand.layout.ty,
243-
cast.ty,
244-
);
245-
OperandValue::Pair(lldata, llextra)
231+
(lldata, None)
246232
}
247233
OperandValue::Ref(..) => {
248234
bug!("by-ref operand {:?} in `codegen_rvalue_operand`", operand);
249235
}
250-
}
236+
};
237+
let (lldata, llextra) =
238+
base::unsize_ptr(&mut bx, lldata, operand.layout.ty, cast.ty, llextra);
239+
OperandValue::Pair(lldata, llextra)
251240
}
252241
mir::CastKind::Pointer(PointerCast::MutToConstPointer)
253242
| mir::CastKind::Misc

compiler/rustc_middle/src/query/mod.rs

+5
Original file line numberDiff line numberDiff line change
@@ -977,6 +977,11 @@ rustc_queries! {
977977
desc { |tcx| "finding all vtable entries for trait {}", tcx.def_path_str(key.def_id()) }
978978
}
979979

980+
query vtable_trait_upcasting_coercion_new_vptr_slot(key: (ty::PolyTraitRef<'tcx>, ty::PolyTraitRef<'tcx>)) -> Option<usize> {
981+
desc { |tcx| "finding the slot within vtable for trait {} vtable ptr during trait upcasting coercion from {} vtable",
982+
tcx.def_path_str(key.1.def_id()), tcx.def_path_str(key.0.def_id()) }
983+
}
984+
980985
query codegen_fulfill_obligation(
981986
key: (ty::ParamEnv<'tcx>, ty::PolyTraitRef<'tcx>)
982987
) -> Result<ImplSource<'tcx, ()>, ErrorReported> {

compiler/rustc_mir/src/interpret/cast.rs

+27-5
Original file line numberDiff line numberDiff line change
@@ -269,12 +269,34 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
269269
Immediate::new_slice(ptr, length.eval_usize(*self.tcx, self.param_env), self);
270270
self.write_immediate(val, dest)
271271
}
272-
(&ty::Dynamic(..), &ty::Dynamic(..)) => {
273-
// For now, upcasts are limited to changes in marker
274-
// traits, and hence never actually require an actual
275-
// change to the vtable.
272+
(&ty::Dynamic(ref data_a, ..), &ty::Dynamic(ref data_b, ..)) => {
276273
let val = self.read_immediate(src)?;
277-
self.write_immediate(*val, dest)
274+
if data_a.principal_def_id() == data_b.principal_def_id() {
275+
return self.write_immediate(*val, dest);
276+
}
277+
// trait upcasting coercion
278+
let principal_a = data_a.principal().expect(
279+
"unsize_into_ptr: missing principal trait for trait upcasting coercion",
280+
);
281+
let principal_b = data_b.principal().expect(
282+
"unsize_into_ptr: missing principal trait for trait upcasting coercion",
283+
);
284+
285+
let vptr_entry_idx = self.tcx.vtable_trait_upcasting_coercion_new_vptr_slot((
286+
principal_a.with_self_ty(*self.tcx, src_pointee_ty),
287+
principal_b.with_self_ty(*self.tcx, src_pointee_ty),
288+
));
289+
290+
if let Some(entry_idx) = vptr_entry_idx {
291+
let entry_idx = u64::try_from(entry_idx).unwrap();
292+
let (old_data, old_vptr) = val.to_scalar_pair()?;
293+
let old_vptr = self.scalar_to_ptr(old_vptr);
294+
let new_vptr = self
295+
.read_new_vtable_after_trait_upcasting_from_vtable(old_vptr, entry_idx)?;
296+
self.write_immediate(Immediate::new_dyn_trait(old_data, new_vptr, self), dest)
297+
} else {
298+
self.write_immediate(*val, dest)
299+
}
278300
}
279301
(_, &ty::Dynamic(ref data, _)) => {
280302
// Initial cast from sized to dyn trait

0 commit comments

Comments
 (0)