Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Miri: basic dyn* support #107728

Merged
merged 6 commits into from
Feb 21, 2023
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 9 additions & 2 deletions compiler/rustc_const_eval/src/interpret/cast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -312,6 +312,8 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
}
}

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

_ => {
span_bug!(self.cur_span(), "invalid unsizing {:?} -> {:?}", src.layout.ty, cast_ty)
span_bug!(
self.cur_span(),
"invalid pointer unsizing {:?} -> {:?}",
src.layout.ty,
cast_ty
)
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion compiler/rustc_const_eval/src/interpret/eval_context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -632,7 +632,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
}
Ok(Some((size, align)))
}
ty::Dynamic(..) => {
ty::Dynamic(_, _, ty::Dyn) => {
let vtable = metadata.unwrap_meta().to_pointer(self)?;
// Read size and align from vtable (already checks size).
Ok(Some(self.get_vtable_size_and_align(vtable)?))
Expand Down
2 changes: 1 addition & 1 deletion compiler/rustc_const_eval/src/interpret/intern.rs
Original file line number Diff line number Diff line change
Expand Up @@ -242,7 +242,7 @@ impl<'rt, 'mir, 'tcx: 'mir, M: CompileTimeMachine<'mir, 'tcx, const_eval::Memory
let mplace = self.ecx.ref_to_mplace(&value)?;
assert_eq!(mplace.layout.ty, referenced_ty);
// Handle trait object vtables.
if let ty::Dynamic(..) =
if let ty::Dynamic(_, _, ty::Dyn) =
tcx.struct_tail_erasing_lifetimes(referenced_ty, self.ecx.param_env).kind()
{
let ptr = mplace.meta.unwrap_meta().to_pointer(&tcx)?;
Expand Down
20 changes: 19 additions & 1 deletion compiler/rustc_const_eval/src/interpret/operand.rs
Original file line number Diff line number Diff line change
Expand Up @@ -255,7 +255,22 @@ impl<'tcx, Prov: Provenance> OpTy<'tcx, Prov> {
}
}

pub fn offset_with_meta(
/// Replace the layout of this operand. There's basically no sanity check that this makes sense,
/// you better know what you are doing! If this is an immediate, applying the wrong layout can
/// not just lead to invalid data, it can actually *shift the data around* since the offsets of
/// a ScalarPair are entirely determined by the layout, not the data.
pub fn transmute(&self, layout: TyAndLayout<'tcx>) -> Self {
assert_eq!(
self.layout.size, layout.size,
"transmuting with a size change, that doesn't seem right"
);
OpTy { layout, ..*self }
}

/// Offset the operand in memory (if possible) and change its metadata.
///
/// This can go wrong very easily if you give the wrong layout for the new place!
pub(super) fn offset_with_meta(
&self,
offset: Size,
meta: MemPlaceMeta<Prov>,
Expand All @@ -276,6 +291,9 @@ impl<'tcx, Prov: Provenance> OpTy<'tcx, Prov> {
}
}

/// Offset the operand in memory (if possible).
///
/// This can go wrong very easily if you give the wrong layout for the new place!
pub fn offset(
&self,
offset: Size,
Expand Down
67 changes: 52 additions & 15 deletions compiler/rustc_const_eval/src/interpret/place.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ pub enum MemPlaceMeta<Prov: Provenance = AllocId> {
}

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

#[inline]
pub fn offset_with_meta<'tcx>(
pub(super) fn offset_with_meta<'tcx>(
self,
offset: Size,
meta: MemPlaceMeta<Prov>,
cx: &impl HasDataLayout,
) -> InterpResult<'tcx, Self> {
debug_assert!(
!meta.has_meta() || self.meta.has_meta(),
"cannot use `offset_with_meta` to add metadata to a place"
);
Ok(MemPlace { ptr: self.ptr.offset(offset, cx)?, meta })
}
}
Expand Down Expand Up @@ -182,8 +187,11 @@ impl<'tcx, Prov: Provenance> MPlaceTy<'tcx, Prov> {
MPlaceTy { mplace: MemPlace { ptr, meta: MemPlaceMeta::None }, layout, align }
}

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

/// Offset the place in memory.
///
/// This can go wrong very easily if you give the wrong layout for the new place!
pub fn offset(
&self,
offset: Size,
Expand Down Expand Up @@ -241,14 +252,6 @@ impl<'tcx, Prov: Provenance> MPlaceTy<'tcx, Prov> {
}
}
}

#[inline]
pub(super) fn vtable(&self) -> Scalar<Prov> {
match self.layout.ty.kind() {
ty::Dynamic(..) => self.mplace.meta.unwrap_meta(),
_ => bug!("vtable not supported on type {:?}", self.layout.ty),
}
}
}

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

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

Expand Down Expand Up @@ -807,11 +820,16 @@ where
}

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

Expand All @@ -820,7 +838,26 @@ where
layout,
align: layout.align.abi,
};
Ok(mplace)
Ok((mplace, vtable))
}

/// Turn an operand with a `dyn* Trait` type into an operand with the actual dynamic type.
/// Aso returns the vtable.
pub(super) fn unpack_dyn_star(
&self,
op: &OpTy<'tcx, M::Provenance>,
) -> InterpResult<'tcx, (OpTy<'tcx, M::Provenance>, Pointer<Option<M::Provenance>>)> {
assert!(
matches!(op.layout.ty.kind(), ty::Dynamic(_, _, ty::DynStar)),
"`unpack_dyn_star` only makes sense on `dyn*` types"
);
let data = self.operand_field(&op, 0)?;
let vtable = self.operand_field(&op, 1)?;
let vtable = self.read_pointer(&vtable)?;
let (ty, _) = self.get_ptr_vtable(vtable)?;
let layout = self.layout_of(ty)?;
let data = data.transmute(layout);
Ok((data, vtable))
}
}

Expand Down
116 changes: 77 additions & 39 deletions compiler/rustc_const_eval/src/interpret/terminator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -538,10 +538,9 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
// pointer or `dyn Trait` type, but it could be wrapped in newtypes. So recursively
// unwrap those newtypes until we are there.
let mut receiver = args[0].clone();
let receiver_place = loop {
let receiver = loop {
match receiver.layout.ty.kind() {
ty::Ref(..) | ty::RawPtr(..) => break self.deref_operand(&receiver)?,
ty::Dynamic(..) => break receiver.assert_mem_place(), // no immediate unsized values
ty::Dynamic(..) | ty::Ref(..) | ty::RawPtr(..) => break receiver,
_ => {
// Not there yet, search for the only non-ZST field.
let mut non_zst_field = None;
Expand All @@ -567,39 +566,74 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
}
}
};
// Obtain the underlying trait we are working on.
let receiver_tail = self
.tcx
.struct_tail_erasing_lifetimes(receiver_place.layout.ty, self.param_env);
let ty::Dynamic(data, ..) = receiver_tail.kind() else {
span_bug!(self.cur_span(), "dynamic call on non-`dyn` type {}", receiver_tail)

// break self.deref_operand(&receiver)?.into();

// Obtain the underlying trait we are working on, and the adjusted receiver argument.
let recv_ty = receiver.layout.ty;
let receiver_place = match recv_ty.kind() {
ty::Ref(..) | ty::RawPtr(..) => self.deref_operand(&receiver)?,
ty::Dynamic(_, _, ty::Dyn) => receiver.assert_mem_place(), // unsized (`dyn`) cannot be immediate
RalfJung marked this conversation as resolved.
Show resolved Hide resolved
ty::Dynamic(_, _, ty::DynStar) => {
// Not clear how to handle this, so far we assume the receiver is always a pointer.
span_bug!(
self.cur_span(),
"by-value calls on a `dyn*`... are those a thing?"
);
}
_ => bug!(),
};
let (vptr, dyn_ty, adjusted_receiver) = if let ty::Dynamic(data, _, ty::DynStar) =
receiver_place.layout.ty.kind()
{
let (recv, vptr) = self.unpack_dyn_star(&receiver_place.into())?;
let (dyn_ty, dyn_trait) = self.get_ptr_vtable(vptr)?;
if dyn_trait != data.principal() {
throw_ub_format!(
"`dyn*` call on a pointer whose vtable does not match its type"
);
}
let recv = recv.assert_mem_place(); // we passed an MPlaceTy to `unpack_dyn_star` so we definitely still have one

// Get the required information from the vtable.
let vptr = receiver_place.meta.unwrap_meta().to_pointer(self)?;
let (dyn_ty, dyn_trait) = self.get_ptr_vtable(vptr)?;
if dyn_trait != data.principal() {
throw_ub_format!(
"`dyn` call on a pointer whose vtable does not match its type"
);
}
(vptr, dyn_ty, recv.ptr)
} else {
// Doesn't have to be a `dyn Trait`, but the unsized tail must be `dyn Trait`.
// (For that reason we also cannot use `unpack_dyn_trait`.)
let receiver_tail = self
.tcx
.struct_tail_erasing_lifetimes(receiver_place.layout.ty, self.param_env);
let ty::Dynamic(data, _, ty::Dyn) = receiver_tail.kind() else {
span_bug!(self.cur_span(), "dynamic call on non-`dyn` type {}", receiver_tail)
};
assert!(receiver_place.layout.is_unsized());

// Get the required information from the vtable.
let vptr = receiver_place.meta.unwrap_meta().to_pointer(self)?;
let (dyn_ty, dyn_trait) = self.get_ptr_vtable(vptr)?;
if dyn_trait != data.principal() {
throw_ub_format!(
"`dyn` call on a pointer whose vtable does not match its type"
);
}

// It might be surprising that we use a pointer as the receiver even if this
// is a by-val case; this works because by-val passing of an unsized `dyn
// Trait` to a function is actually desugared to a pointer.
(vptr, dyn_ty, receiver_place.ptr)
};

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

let trait_def_id = tcx.trait_of_item(def_id).unwrap();
let virtual_trait_ref =
ty::TraitRef::from_method(tcx, trait_def_id, instance.substs);
assert_eq!(
receiver_tail,
virtual_trait_ref.self_ty(),
"mismatch in underlying dyn trait computation within Miri and MIR building",
);
let existential_trait_ref =
ty::ExistentialTraitRef::erase_self_ty(tcx, virtual_trait_ref);
let concrete_trait_ref = existential_trait_ref.with_self_ty(tcx, dyn_ty);
Expand All @@ -614,17 +648,12 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
assert_eq!(fn_inst, concrete_method);
}

// `*mut receiver_place.layout.ty` is almost the layout that we
// want for args[0]: We have to project to field 0 because we want
// a thin pointer.
assert!(receiver_place.layout.is_unsized());
let receiver_ptr_ty = self.tcx.mk_mut_ptr(receiver_place.layout.ty);
let this_receiver_ptr = self.layout_of(receiver_ptr_ty)?.field(self, 0);
// Adjust receiver argument.
args[0] = OpTy::from(ImmTy::from_immediate(
Scalar::from_maybe_pointer(receiver_place.ptr, self).into(),
this_receiver_ptr,
));
// Adjust receiver argument. Layout can be any (thin) ptr.
args[0] = ImmTy::from_immediate(
Scalar::from_maybe_pointer(adjusted_receiver, self).into(),
self.layout_of(self.tcx.mk_mut_ptr(dyn_ty))?,
)
.into();
trace!("Patched receiver operand to {:#?}", args[0]);
// recurse with concrete function
self.eval_fn_call(
Expand Down Expand Up @@ -653,15 +682,24 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
// implementation fail -- a problem shared by rustc.
let place = self.force_allocation(place)?;

let (instance, place) = match place.layout.ty.kind() {
ty::Dynamic(..) => {
let place = match place.layout.ty.kind() {
ty::Dynamic(_, _, ty::Dyn) => {
// Dropping a trait object. Need to find actual drop fn.
let place = self.unpack_dyn_trait(&place)?;
let instance = ty::Instance::resolve_drop_in_place(*self.tcx, place.layout.ty);
(instance, place)
self.unpack_dyn_trait(&place)?.0
}
ty::Dynamic(_, _, ty::DynStar) => {
// Dropping a `dyn*`. Need to find actual drop fn.
self.unpack_dyn_star(&place.into())?.0.assert_mem_place()
}
_ => {
debug_assert_eq!(
instance,
ty::Instance::resolve_drop_in_place(*self.tcx, place.layout.ty)
);
place
}
_ => (instance, place),
};
let instance = ty::Instance::resolve_drop_in_place(*self.tcx, place.layout.ty);
let fn_abi = self.fn_abi_of_instance(instance, ty::List::empty())?;

let arg = ImmTy::from_immediate(
Expand Down
Loading