Skip to content

Commit b2f5814

Browse files
committed
basic dyn* support for Miri
1 parent dc89a80 commit b2f5814

16 files changed

+325
-84
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

+87-40
Original file line numberDiff line numberDiff line change
@@ -538,10 +538,9 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
538538
// pointer or `dyn Trait` type, but it could be wrapped in newtypes. So recursively
539539
// unwrap those newtypes until we are there.
540540
let mut receiver = args[0].clone();
541-
let receiver_place = loop {
541+
let receiver = loop {
542542
match receiver.layout.ty.kind() {
543-
ty::Ref(..) | ty::RawPtr(..) => break self.deref_operand(&receiver)?,
544-
ty::Dynamic(..) => break receiver.assert_mem_place(), // no immediate unsized values
543+
ty::Dynamic(..) | ty::Ref(..) | ty::RawPtr(..) => break receiver,
545544
_ => {
546545
// Not there yet, search for the only non-ZST field.
547546
let mut non_zst_field = None;
@@ -567,39 +566,83 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
567566
}
568567
}
569568
};
570-
// Obtain the underlying trait we are working on.
571-
let receiver_tail = self
572-
.tcx
573-
.struct_tail_erasing_lifetimes(receiver_place.layout.ty, self.param_env);
574-
let ty::Dynamic(data, ..) = receiver_tail.kind() else {
575-
span_bug!(self.cur_span(), "dynamic call on non-`dyn` type {}", receiver_tail)
576-
};
577569

578-
// Get the required information from the vtable.
579-
let vptr = receiver_place.meta.unwrap_meta().to_pointer(self)?;
580-
let (dyn_ty, dyn_trait) = self.get_ptr_vtable(vptr)?;
581-
if dyn_trait != data.principal() {
582-
throw_ub_format!(
583-
"`dyn` call on a pointer whose vtable does not match its type"
584-
);
585-
}
570+
// break self.deref_operand(&receiver)?.into();
571+
572+
// Obtain the underlying trait we are working on, and the adjusted receiver argument.
573+
let recv_ty = receiver.layout.ty;
574+
let (vptr, dyn_ty, adjusted_receiver) = match recv_ty.kind() {
575+
ty::Ref(..) | ty::RawPtr(..)
576+
if matches!(
577+
recv_ty.builtin_deref(true).unwrap().ty.kind(),
578+
ty::Dynamic(_, _, ty::DynStar)
579+
) =>
580+
{
581+
let receiver = self.deref_operand(&receiver)?;
582+
let ty::Dynamic(data, ..) = receiver.layout.ty.kind() else { bug!() };
583+
let (recv, vptr) = self.unpack_dyn_star(&receiver.into())?;
584+
let (dyn_ty, dyn_trait) = self.get_ptr_vtable(vptr)?;
585+
if dyn_trait != data.principal() {
586+
throw_ub_format!(
587+
"`dyn*` call on a pointer whose vtable does not match its type"
588+
);
589+
}
590+
let recv = recv.assert_mem_place(); // we passed an MPlaceTy to `unpack_dyn_star` so we definitely still have one
591+
592+
(vptr, dyn_ty, recv.ptr)
593+
}
594+
ty::Dynamic(_, _, ty::DynStar) => {
595+
// Not clear how to handle this, so far we assume the receiver is always a pointer.
596+
span_bug!(
597+
self.cur_span(),
598+
"by-value calls on a `dyn*`... are those a thing?"
599+
);
600+
}
601+
_ => {
602+
let receiver_place = match recv_ty.kind() {
603+
ty::Ref(..) | ty::RawPtr(..) => self.deref_operand(&receiver)?,
604+
ty::Dynamic(_, _, ty::Dyn) => receiver.assert_mem_place(), // unsized (`dyn`) cannot be immediate
605+
_ => bug!(),
606+
};
607+
// Doesn't have to be a `dyn Trait`, but the unsized tail must be `dyn Trait`.
608+
// (For that reason we also cannot use `unpack_dyn_trait`.)
609+
let receiver_tail = self.tcx.struct_tail_erasing_lifetimes(
610+
receiver_place.layout.ty,
611+
self.param_env,
612+
);
613+
let ty::Dynamic(data, _, ty::Dyn) = receiver_tail.kind() else {
614+
span_bug!(self.cur_span(), "dynamic call on non-`dyn` type {}", receiver_tail)
615+
};
616+
assert!(receiver_place.layout.is_unsized());
617+
618+
// Get the required information from the vtable.
619+
let vptr = receiver_place.meta.unwrap_meta().to_pointer(self)?;
620+
let (dyn_ty, dyn_trait) = self.get_ptr_vtable(vptr)?;
621+
if dyn_trait != data.principal() {
622+
throw_ub_format!(
623+
"`dyn` call on a pointer whose vtable does not match its type"
624+
);
625+
}
626+
627+
// It might be surprising that we use a pointer as the receiver even if this
628+
// is a by-val case; this works because by-val passing of an unsized `dyn
629+
// Trait` to a function is actually desugared to a pointer.
630+
(vptr, dyn_ty, receiver_place.ptr)
631+
}
632+
};
586633

587634
// Now determine the actual method to call. We can do that in two different ways and
588635
// compare them to ensure everything fits.
589636
let Some(ty::VtblEntry::Method(fn_inst)) = self.get_vtable_entries(vptr)?.get(idx).copied() else {
590637
throw_ub_format!("`dyn` call trying to call something that is not a method")
591638
};
639+
trace!("Virtual call dispatches to {fn_inst:#?}");
592640
if cfg!(debug_assertions) {
593641
let tcx = *self.tcx;
594642

595643
let trait_def_id = tcx.trait_of_item(def_id).unwrap();
596644
let virtual_trait_ref =
597645
ty::TraitRef::from_method(tcx, trait_def_id, instance.substs);
598-
assert_eq!(
599-
receiver_tail,
600-
virtual_trait_ref.self_ty(),
601-
"mismatch in underlying dyn trait computation within Miri and MIR building",
602-
);
603646
let existential_trait_ref =
604647
ty::ExistentialTraitRef::erase_self_ty(tcx, virtual_trait_ref);
605648
let concrete_trait_ref = existential_trait_ref.with_self_ty(tcx, dyn_ty);
@@ -614,17 +657,12 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
614657
assert_eq!(fn_inst, concrete_method);
615658
}
616659

617-
// `*mut receiver_place.layout.ty` is almost the layout that we
618-
// want for args[0]: We have to project to field 0 because we want
619-
// a thin pointer.
620-
assert!(receiver_place.layout.is_unsized());
621-
let receiver_ptr_ty = self.tcx.mk_mut_ptr(receiver_place.layout.ty);
622-
let this_receiver_ptr = self.layout_of(receiver_ptr_ty)?.field(self, 0);
623-
// Adjust receiver argument.
624-
args[0] = OpTy::from(ImmTy::from_immediate(
625-
Scalar::from_maybe_pointer(receiver_place.ptr, self).into(),
626-
this_receiver_ptr,
627-
));
660+
// Adjust receiver argument. Layout can be any (thin) ptr.
661+
args[0] = ImmTy::from_immediate(
662+
Scalar::from_maybe_pointer(adjusted_receiver, self).into(),
663+
self.layout_of(self.tcx.mk_mut_ptr(dyn_ty))?,
664+
)
665+
.into();
628666
trace!("Patched receiver operand to {:#?}", args[0]);
629667
// recurse with concrete function
630668
self.eval_fn_call(
@@ -653,15 +691,24 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
653691
// implementation fail -- a problem shared by rustc.
654692
let place = self.force_allocation(place)?;
655693

656-
let (instance, place) = match place.layout.ty.kind() {
657-
ty::Dynamic(..) => {
694+
let place = match place.layout.ty.kind() {
695+
ty::Dynamic(_, _, ty::Dyn) => {
658696
// Dropping a trait object. Need to find actual drop fn.
659-
let place = self.unpack_dyn_trait(&place)?;
660-
let instance = ty::Instance::resolve_drop_in_place(*self.tcx, place.layout.ty);
661-
(instance, place)
697+
self.unpack_dyn_trait(&place)?.0
698+
}
699+
ty::Dynamic(_, _, ty::DynStar) => {
700+
// Dropping a `dyn*`. Need to find actual drop fn.
701+
self.unpack_dyn_star(&place.into())?.0.assert_mem_place()
702+
}
703+
_ => {
704+
debug_assert_eq!(
705+
instance,
706+
ty::Instance::resolve_drop_in_place(*self.tcx, place.layout.ty)
707+
);
708+
place
662709
}
663-
_ => (instance, place),
664710
};
711+
let instance = ty::Instance::resolve_drop_in_place(*self.tcx, place.layout.ty);
665712
let fn_abi = self.fn_abi_of_instance(instance, ty::List::empty())?;
666713

667714
let arg = ImmTy::from_immediate(

0 commit comments

Comments
 (0)