Skip to content

Commit f715e43

Browse files
committed
Auto merge of #107728 - RalfJung:miri-dyn-star, r=RalfJung,oli-obk
Miri: basic dyn* support As usual I am very unsure about the dynamic dispatch stuff, but it passes even the `Pin<&mut dyn* Trait>` test so that is something. TBH I think it was a mistake to make `dyn Trait` and `dyn* Trait` part of the same `TyKind` variant. Almost everywhere in Miri this lead to the wrong default behavior, resulting in strange ICEs instead of nice "unimplemented" messages. The two types describe pretty different runtime data layout after all. Strangely I did not need to do the equivalent of [this diff](#106532 (comment)) in Miri. Maybe that is because the unsizing logic matches on `ty::Dynamic(.., ty::Dyn)` already? In `unsized_info` I don't think the `target_dyn_kind` can be `DynStar`, since then it wouldn't be unsized! r? `@oli-obk` Cc `@eholk` (dyn-star) #102425
2 parents 2deff71 + 054c76d commit f715e43

18 files changed

+320
-85
lines changed

compiler/rustc_const_eval/src/interpret/cast.rs

+9-2
Original file line numberDiff line numberDiff line change
@@ -312,6 +312,8 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
312312
}
313313
}
314314

315+
/// `src` is a *pointer to* a `source_ty`, and in `dest` we should store a pointer to th same
316+
/// data at type `cast_ty`.
315317
fn unsize_into_ptr(
316318
&mut self,
317319
src: &OpTy<'tcx, M::Provenance>,
@@ -335,7 +337,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
335337
);
336338
self.write_immediate(val, dest)
337339
}
338-
(ty::Dynamic(data_a, ..), ty::Dynamic(data_b, ..)) => {
340+
(ty::Dynamic(data_a, _, ty::Dyn), ty::Dynamic(data_b, _, ty::Dyn)) => {
339341
let val = self.read_immediate(src)?;
340342
if data_a.principal() == data_b.principal() {
341343
// A NOP cast that doesn't actually change anything, should be allowed even with mismatching vtables.
@@ -359,7 +361,12 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
359361
}
360362

361363
_ => {
362-
span_bug!(self.cur_span(), "invalid unsizing {:?} -> {:?}", src.layout.ty, cast_ty)
364+
span_bug!(
365+
self.cur_span(),
366+
"invalid pointer unsizing {:?} -> {:?}",
367+
src.layout.ty,
368+
cast_ty
369+
)
363370
}
364371
}
365372
}

compiler/rustc_const_eval/src/interpret/eval_context.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -632,7 +632,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
632632
}
633633
Ok(Some((size, align)))
634634
}
635-
ty::Dynamic(..) => {
635+
ty::Dynamic(_, _, ty::Dyn) => {
636636
let vtable = metadata.unwrap_meta().to_pointer(self)?;
637637
// Read size and align from vtable (already checks size).
638638
Ok(Some(self.get_vtable_size_and_align(vtable)?))

compiler/rustc_const_eval/src/interpret/intern.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -242,7 +242,7 @@ impl<'rt, 'mir, 'tcx: 'mir, M: CompileTimeMachine<'mir, 'tcx, const_eval::Memory
242242
let mplace = self.ecx.ref_to_mplace(&value)?;
243243
assert_eq!(mplace.layout.ty, referenced_ty);
244244
// Handle trait object vtables.
245-
if let ty::Dynamic(..) =
245+
if let ty::Dynamic(_, _, ty::Dyn) =
246246
tcx.struct_tail_erasing_lifetimes(referenced_ty, self.ecx.param_env).kind()
247247
{
248248
let ptr = mplace.meta.unwrap_meta().to_pointer(&tcx)?;

compiler/rustc_const_eval/src/interpret/operand.rs

+19-1
Original file line numberDiff line numberDiff line change
@@ -255,7 +255,22 @@ impl<'tcx, Prov: Provenance> OpTy<'tcx, Prov> {
255255
}
256256
}
257257

258-
pub fn offset_with_meta(
258+
/// Replace the layout of this operand. There's basically no sanity check that this makes sense,
259+
/// you better know what you are doing! If this is an immediate, applying the wrong layout can
260+
/// not just lead to invalid data, it can actually *shift the data around* since the offsets of
261+
/// a ScalarPair are entirely determined by the layout, not the data.
262+
pub fn transmute(&self, layout: TyAndLayout<'tcx>) -> Self {
263+
assert_eq!(
264+
self.layout.size, layout.size,
265+
"transmuting with a size change, that doesn't seem right"
266+
);
267+
OpTy { layout, ..*self }
268+
}
269+
270+
/// Offset the operand in memory (if possible) and change its metadata.
271+
///
272+
/// This can go wrong very easily if you give the wrong layout for the new place!
273+
pub(super) fn offset_with_meta(
259274
&self,
260275
offset: Size,
261276
meta: MemPlaceMeta<Prov>,
@@ -276,6 +291,9 @@ impl<'tcx, Prov: Provenance> OpTy<'tcx, Prov> {
276291
}
277292
}
278293

294+
/// Offset the operand in memory (if possible).
295+
///
296+
/// This can go wrong very easily if you give the wrong layout for the new place!
279297
pub fn offset(
280298
&self,
281299
offset: Size,

compiler/rustc_const_eval/src/interpret/place.rs

+52-15
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ pub enum MemPlaceMeta<Prov: Provenance = AllocId> {
2626
}
2727

2828
impl<Prov: Provenance> MemPlaceMeta<Prov> {
29+
#[cfg_attr(debug_assertions, track_caller)] // only in debug builds due to perf (see #98980)
2930
pub fn unwrap_meta(self) -> Scalar<Prov> {
3031
match self {
3132
Self::Meta(s) => s,
@@ -147,12 +148,16 @@ impl<Prov: Provenance> MemPlace<Prov> {
147148
}
148149

149150
#[inline]
150-
pub fn offset_with_meta<'tcx>(
151+
pub(super) fn offset_with_meta<'tcx>(
151152
self,
152153
offset: Size,
153154
meta: MemPlaceMeta<Prov>,
154155
cx: &impl HasDataLayout,
155156
) -> InterpResult<'tcx, Self> {
157+
debug_assert!(
158+
!meta.has_meta() || self.meta.has_meta(),
159+
"cannot use `offset_with_meta` to add metadata to a place"
160+
);
156161
Ok(MemPlace { ptr: self.ptr.offset(offset, cx)?, meta })
157162
}
158163
}
@@ -182,8 +187,11 @@ impl<'tcx, Prov: Provenance> MPlaceTy<'tcx, Prov> {
182187
MPlaceTy { mplace: MemPlace { ptr, meta: MemPlaceMeta::None }, layout, align }
183188
}
184189

190+
/// Offset the place in memory and change its metadata.
191+
///
192+
/// This can go wrong very easily if you give the wrong layout for the new place!
185193
#[inline]
186-
pub fn offset_with_meta(
194+
pub(crate) fn offset_with_meta(
187195
&self,
188196
offset: Size,
189197
meta: MemPlaceMeta<Prov>,
@@ -197,6 +205,9 @@ impl<'tcx, Prov: Provenance> MPlaceTy<'tcx, Prov> {
197205
})
198206
}
199207

208+
/// Offset the place in memory.
209+
///
210+
/// This can go wrong very easily if you give the wrong layout for the new place!
200211
pub fn offset(
201212
&self,
202213
offset: Size,
@@ -241,14 +252,6 @@ impl<'tcx, Prov: Provenance> MPlaceTy<'tcx, Prov> {
241252
}
242253
}
243254
}
244-
245-
#[inline]
246-
pub(super) fn vtable(&self) -> Scalar<Prov> {
247-
match self.layout.ty.kind() {
248-
ty::Dynamic(..) => self.mplace.meta.unwrap_meta(),
249-
_ => bug!("vtable not supported on type {:?}", self.layout.ty),
250-
}
251-
}
252255
}
253256

254257
// These are defined here because they produce a place.
@@ -266,7 +269,12 @@ impl<'tcx, Prov: Provenance> OpTy<'tcx, Prov> {
266269
#[inline(always)]
267270
#[cfg_attr(debug_assertions, track_caller)] // only in debug builds due to perf (see #98980)
268271
pub fn assert_mem_place(&self) -> MPlaceTy<'tcx, Prov> {
269-
self.as_mplace_or_imm().left().unwrap()
272+
self.as_mplace_or_imm().left().unwrap_or_else(|| {
273+
bug!(
274+
"OpTy of type {} was immediate when it was expected to be an MPlace",
275+
self.layout.ty
276+
)
277+
})
270278
}
271279
}
272280

@@ -283,7 +291,12 @@ impl<'tcx, Prov: Provenance> PlaceTy<'tcx, Prov> {
283291
#[inline(always)]
284292
#[cfg_attr(debug_assertions, track_caller)] // only in debug builds due to perf (see #98980)
285293
pub fn assert_mem_place(&self) -> MPlaceTy<'tcx, Prov> {
286-
self.as_mplace_or_local().left().unwrap()
294+
self.as_mplace_or_local().left().unwrap_or_else(|| {
295+
bug!(
296+
"PlaceTy of type {} was a local when it was expected to be an MPlace",
297+
self.layout.ty
298+
)
299+
})
287300
}
288301
}
289302

@@ -807,11 +820,16 @@ where
807820
}
808821

809822
/// Turn a place with a `dyn Trait` type into a place with the actual dynamic type.
823+
/// Aso returns the vtable.
810824
pub(super) fn unpack_dyn_trait(
811825
&self,
812826
mplace: &MPlaceTy<'tcx, M::Provenance>,
813-
) -> InterpResult<'tcx, MPlaceTy<'tcx, M::Provenance>> {
814-
let vtable = mplace.vtable().to_pointer(self)?; // also sanity checks the type
827+
) -> InterpResult<'tcx, (MPlaceTy<'tcx, M::Provenance>, Pointer<Option<M::Provenance>>)> {
828+
assert!(
829+
matches!(mplace.layout.ty.kind(), ty::Dynamic(_, _, ty::Dyn)),
830+
"`unpack_dyn_trait` only makes sense on `dyn*` types"
831+
);
832+
let vtable = mplace.meta.unwrap_meta().to_pointer(self)?;
815833
let (ty, _) = self.get_ptr_vtable(vtable)?;
816834
let layout = self.layout_of(ty)?;
817835

@@ -820,7 +838,26 @@ where
820838
layout,
821839
align: layout.align.abi,
822840
};
823-
Ok(mplace)
841+
Ok((mplace, vtable))
842+
}
843+
844+
/// Turn an operand with a `dyn* Trait` type into an operand with the actual dynamic type.
845+
/// Aso returns the vtable.
846+
pub(super) fn unpack_dyn_star(
847+
&self,
848+
op: &OpTy<'tcx, M::Provenance>,
849+
) -> InterpResult<'tcx, (OpTy<'tcx, M::Provenance>, Pointer<Option<M::Provenance>>)> {
850+
assert!(
851+
matches!(op.layout.ty.kind(), ty::Dynamic(_, _, ty::DynStar)),
852+
"`unpack_dyn_star` only makes sense on `dyn*` types"
853+
);
854+
let data = self.operand_field(&op, 0)?;
855+
let vtable = self.operand_field(&op, 1)?;
856+
let vtable = self.read_pointer(&vtable)?;
857+
let (ty, _) = self.get_ptr_vtable(vtable)?;
858+
let layout = self.layout_of(ty)?;
859+
let data = data.transmute(layout);
860+
Ok((data, vtable))
824861
}
825862
}
826863

compiler/rustc_const_eval/src/interpret/terminator.rs

+69-38
Original file line numberDiff line numberDiff line change
@@ -547,7 +547,14 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
547547
let receiver_place = loop {
548548
match receiver.layout.ty.kind() {
549549
ty::Ref(..) | ty::RawPtr(..) => break self.deref_operand(&receiver)?,
550-
ty::Dynamic(..) => break receiver.assert_mem_place(), // no immediate unsized values
550+
ty::Dynamic(.., ty::Dyn) => break receiver.assert_mem_place(), // no immediate unsized values
551+
ty::Dynamic(.., ty::DynStar) => {
552+
// Not clear how to handle this, so far we assume the receiver is always a pointer.
553+
span_bug!(
554+
self.cur_span(),
555+
"by-value calls on a `dyn*`... are those a thing?"
556+
);
557+
}
551558
_ => {
552559
// Not there yet, search for the only non-ZST field.
553560
let mut non_zst_field = None;
@@ -573,39 +580,59 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
573580
}
574581
}
575582
};
576-
// Obtain the underlying trait we are working on.
577-
let receiver_tail = self
578-
.tcx
579-
.struct_tail_erasing_lifetimes(receiver_place.layout.ty, self.param_env);
580-
let ty::Dynamic(data, ..) = receiver_tail.kind() else {
581-
span_bug!(self.cur_span(), "dynamic call on non-`dyn` type {}", receiver_tail)
582-
};
583583

584-
// Get the required information from the vtable.
585-
let vptr = receiver_place.meta.unwrap_meta().to_pointer(self)?;
586-
let (dyn_ty, dyn_trait) = self.get_ptr_vtable(vptr)?;
587-
if dyn_trait != data.principal() {
588-
throw_ub_format!(
589-
"`dyn` call on a pointer whose vtable does not match its type"
590-
);
591-
}
584+
// Obtain the underlying trait we are working on, and the adjusted receiver argument.
585+
let (vptr, dyn_ty, adjusted_receiver) = if let ty::Dynamic(data, _, ty::DynStar) =
586+
receiver_place.layout.ty.kind()
587+
{
588+
let (recv, vptr) = self.unpack_dyn_star(&receiver_place.into())?;
589+
let (dyn_ty, dyn_trait) = self.get_ptr_vtable(vptr)?;
590+
if dyn_trait != data.principal() {
591+
throw_ub_format!(
592+
"`dyn*` call on a pointer whose vtable does not match its type"
593+
);
594+
}
595+
let recv = recv.assert_mem_place(); // we passed an MPlaceTy to `unpack_dyn_star` so we definitely still have one
596+
597+
(vptr, dyn_ty, recv.ptr)
598+
} else {
599+
// Doesn't have to be a `dyn Trait`, but the unsized tail must be `dyn Trait`.
600+
// (For that reason we also cannot use `unpack_dyn_trait`.)
601+
let receiver_tail = self
602+
.tcx
603+
.struct_tail_erasing_lifetimes(receiver_place.layout.ty, self.param_env);
604+
let ty::Dynamic(data, _, ty::Dyn) = receiver_tail.kind() else {
605+
span_bug!(self.cur_span(), "dynamic call on non-`dyn` type {}", receiver_tail)
606+
};
607+
assert!(receiver_place.layout.is_unsized());
608+
609+
// Get the required information from the vtable.
610+
let vptr = receiver_place.meta.unwrap_meta().to_pointer(self)?;
611+
let (dyn_ty, dyn_trait) = self.get_ptr_vtable(vptr)?;
612+
if dyn_trait != data.principal() {
613+
throw_ub_format!(
614+
"`dyn` call on a pointer whose vtable does not match its type"
615+
);
616+
}
617+
618+
// It might be surprising that we use a pointer as the receiver even if this
619+
// is a by-val case; this works because by-val passing of an unsized `dyn
620+
// Trait` to a function is actually desugared to a pointer.
621+
(vptr, dyn_ty, receiver_place.ptr)
622+
};
592623

593624
// Now determine the actual method to call. We can do that in two different ways and
594625
// compare them to ensure everything fits.
595626
let Some(ty::VtblEntry::Method(fn_inst)) = self.get_vtable_entries(vptr)?.get(idx).copied() else {
596627
throw_ub_format!("`dyn` call trying to call something that is not a method")
597628
};
629+
trace!("Virtual call dispatches to {fn_inst:#?}");
598630
if cfg!(debug_assertions) {
599631
let tcx = *self.tcx;
600632

601633
let trait_def_id = tcx.trait_of_item(def_id).unwrap();
602634
let virtual_trait_ref =
603635
ty::TraitRef::from_method(tcx, trait_def_id, instance.substs);
604-
assert_eq!(
605-
receiver_tail,
606-
virtual_trait_ref.self_ty(),
607-
"mismatch in underlying dyn trait computation within Miri and MIR building",
608-
);
609636
let existential_trait_ref =
610637
ty::ExistentialTraitRef::erase_self_ty(tcx, virtual_trait_ref);
611638
let concrete_trait_ref = existential_trait_ref.with_self_ty(tcx, dyn_ty);
@@ -620,17 +647,12 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
620647
assert_eq!(fn_inst, concrete_method);
621648
}
622649

623-
// `*mut receiver_place.layout.ty` is almost the layout that we
624-
// want for args[0]: We have to project to field 0 because we want
625-
// a thin pointer.
626-
assert!(receiver_place.layout.is_unsized());
627-
let receiver_ptr_ty = self.tcx.mk_mut_ptr(receiver_place.layout.ty);
628-
let this_receiver_ptr = self.layout_of(receiver_ptr_ty)?.field(self, 0);
629-
// Adjust receiver argument.
630-
args[0] = OpTy::from(ImmTy::from_immediate(
631-
Scalar::from_maybe_pointer(receiver_place.ptr, self).into(),
632-
this_receiver_ptr,
633-
));
650+
// Adjust receiver argument. Layout can be any (thin) ptr.
651+
args[0] = ImmTy::from_immediate(
652+
Scalar::from_maybe_pointer(adjusted_receiver, self).into(),
653+
self.layout_of(self.tcx.mk_mut_ptr(dyn_ty))?,
654+
)
655+
.into();
634656
trace!("Patched receiver operand to {:#?}", args[0]);
635657
// recurse with concrete function
636658
self.eval_fn_call(
@@ -659,15 +681,24 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
659681
// implementation fail -- a problem shared by rustc.
660682
let place = self.force_allocation(place)?;
661683

662-
let (instance, place) = match place.layout.ty.kind() {
663-
ty::Dynamic(..) => {
684+
let place = match place.layout.ty.kind() {
685+
ty::Dynamic(_, _, ty::Dyn) => {
664686
// Dropping a trait object. Need to find actual drop fn.
665-
let place = self.unpack_dyn_trait(&place)?;
666-
let instance = ty::Instance::resolve_drop_in_place(*self.tcx, place.layout.ty);
667-
(instance, place)
687+
self.unpack_dyn_trait(&place)?.0
688+
}
689+
ty::Dynamic(_, _, ty::DynStar) => {
690+
// Dropping a `dyn*`. Need to find actual drop fn.
691+
self.unpack_dyn_star(&place.into())?.0.assert_mem_place()
692+
}
693+
_ => {
694+
debug_assert_eq!(
695+
instance,
696+
ty::Instance::resolve_drop_in_place(*self.tcx, place.layout.ty)
697+
);
698+
place
668699
}
669-
_ => (instance, place),
670700
};
701+
let instance = ty::Instance::resolve_drop_in_place(*self.tcx, place.layout.ty);
671702
let fn_abi = self.fn_abi_of_instance(instance, ty::List::empty())?;
672703

673704
let arg = ImmTy::from_immediate(

0 commit comments

Comments
 (0)