Skip to content

Commit 57c24d0

Browse files
committed
make use of symbolic vtables in interpreter
1 parent 518368c commit 57c24d0

26 files changed

+298
-382
lines changed

compiler/rustc_const_eval/src/interpret/cast.rs

Lines changed: 20 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -299,29 +299,35 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
299299
}
300300
(&ty::Dynamic(ref data_a, ..), &ty::Dynamic(ref data_b, ..)) => {
301301
let val = self.read_immediate(src)?;
302+
let (old_data, old_vptr) = val.to_scalar_pair()?;
303+
let old_vptr = self.scalar_to_ptr(old_vptr)?;
302304
if data_a.principal_def_id() == data_b.principal_def_id() {
303305
return self.write_immediate(*val, dest);
304306
}
305307
// trait upcasting coercion
306-
let vptr_entry_idx = self.tcx.vtable_trait_upcasting_coercion_new_vptr_slot((
308+
let Some(vptr_entry_idx) = self.tcx.vtable_trait_upcasting_coercion_new_vptr_slot((
307309
src_pointee_ty,
308310
dest_pointee_ty,
309-
));
310-
311-
if let Some(entry_idx) = vptr_entry_idx {
312-
let entry_idx = u64::try_from(entry_idx).unwrap();
313-
let (old_data, old_vptr) = val.to_scalar_pair()?;
314-
let old_vptr = self.scalar_to_ptr(old_vptr)?;
315-
let new_vptr = self
316-
.read_new_vtable_after_trait_upcasting_from_vtable(old_vptr, entry_idx)?;
317-
self.write_immediate(Immediate::new_dyn_trait(old_data, new_vptr, self), dest)
318-
} else {
319-
self.write_immediate(*val, dest)
320-
}
311+
)) else {
312+
return self.write_immediate(*val, dest);
313+
};
314+
315+
let (ty, _) = self.get_ptr_vtable(old_vptr)?;
316+
let Some(ty::VtblEntry::TraitVPtr(new_trait)) = self.get_vtable_entries(old_vptr)?.get(vptr_entry_idx) else {
317+
throw_ub_format!(
318+
"upcasting to index {vptr_entry_idx} of vtable {old_vptr} but \
319+
that vtable is too small or does not have an upcast-vtable at that index"
320+
)
321+
};
322+
let new_trait = new_trait.map_bound(|trait_ref| {
323+
ty::ExistentialTraitRef::erase_self_ty(*self.tcx, trait_ref)
324+
});
325+
let new_vptr = self.get_vtable_ptr(ty, Some(new_trait))?;
326+
self.write_immediate(Immediate::new_dyn_trait(old_data, new_vptr, self), dest)
321327
}
322328
(_, &ty::Dynamic(ref data, _)) => {
323329
// Initial cast from sized to dyn trait
324-
let vtable = self.get_vtable(src_pointee_ty, data.principal())?;
330+
let vtable = self.get_vtable_ptr(src_pointee_ty, data.principal())?;
325331
let ptr = self.read_immediate(src)?.to_scalar()?;
326332
let val = Immediate::new_dyn_trait(ptr, vtable, &*self.tcx);
327333
self.write_immediate(val, dest)

compiler/rustc_const_eval/src/interpret/eval_context.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -635,7 +635,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
635635
ty::Dynamic(..) => {
636636
let vtable = self.scalar_to_ptr(metadata.unwrap_meta())?;
637637
// Read size and align from vtable (already checks size).
638-
Ok(Some(self.read_size_and_align_from_vtable(vtable)?))
638+
Ok(Some(self.get_vtable_size_and_align(vtable)?))
639639
}
640640

641641
ty::Slice(_) | ty::Str => {

compiler/rustc_const_eval/src/interpret/memory.rs

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ use std::ptr;
1616
use rustc_ast::Mutability;
1717
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
1818
use rustc_middle::mir::display_allocation;
19-
use rustc_middle::ty::{Instance, ParamEnv, TyCtxt};
19+
use rustc_middle::ty::{self, Instance, ParamEnv, Ty, TyCtxt};
2020
use rustc_target::abi::{Align, HasDataLayout, Size};
2121

2222
use super::{
@@ -496,6 +496,8 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
496496
// contains a reference to memory that was created during its evaluation (i.e., not
497497
// to another static), those inner references only exist in "resolved" form.
498498
if self.tcx.is_foreign_item(def_id) {
499+
// This is unreachable in Miri, but can happen in CTFE where we actually *do* support
500+
// referencing arbitrary (declared) extern statics.
499501
throw_unsup!(ReadExternStatic(def_id));
500502
}
501503

@@ -666,11 +668,13 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
666668
// Can't do this in the match argument, we may get cycle errors since the lock would
667669
// be held throughout the match.
668670
match self.tcx.try_get_global_alloc(id) {
669-
Some(GlobalAlloc::Static(did)) => {
670-
assert!(!self.tcx.is_thread_local_static(did));
671+
Some(GlobalAlloc::Static(def_id)) => {
672+
assert!(self.tcx.is_static(def_id));
673+
assert!(!self.tcx.is_thread_local_static(def_id));
671674
// Use size and align of the type.
672-
let ty = self.tcx.type_of(did);
675+
let ty = self.tcx.type_of(def_id);
673676
let layout = self.tcx.layout_of(ParamEnv::empty().and(ty)).unwrap();
677+
assert!(!layout.is_unsized());
674678
(layout.size, layout.align.abi, AllocKind::LiveData)
675679
}
676680
Some(GlobalAlloc::Memory(alloc)) => {
@@ -681,8 +685,8 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
681685
}
682686
Some(GlobalAlloc::Function(_)) => bug!("We already checked function pointers above"),
683687
Some(GlobalAlloc::Vtable(..)) => {
684-
// No data to be accessed here.
685-
return (Size::ZERO, Align::ONE, AllocKind::Vtable);
688+
// No data to be accessed here. But vtables are pointer-aligned.
689+
return (Size::ZERO, self.tcx.data_layout.pointer_align.abi, AllocKind::Vtable);
686690
}
687691
// The rest must be dead.
688692
None => {
@@ -722,7 +726,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
722726
&self,
723727
ptr: Pointer<Option<M::PointerTag>>,
724728
) -> InterpResult<'tcx, FnVal<'tcx, M::ExtraFnVal>> {
725-
trace!("get_fn({:?})", ptr);
729+
trace!("get_ptr_fn({:?})", ptr);
726730
let (alloc_id, offset, _tag) = self.ptr_get_alloc_id(ptr)?;
727731
if offset.bytes() != 0 {
728732
throw_ub!(InvalidFunctionPointer(Pointer::new(alloc_id, offset)))
@@ -731,6 +735,21 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
731735
.ok_or_else(|| err_ub!(InvalidFunctionPointer(Pointer::new(alloc_id, offset))).into())
732736
}
733737

738+
pub fn get_ptr_vtable(
739+
&self,
740+
ptr: Pointer<Option<M::PointerTag>>,
741+
) -> InterpResult<'tcx, (Ty<'tcx>, Option<ty::PolyExistentialTraitRef<'tcx>>)> {
742+
trace!("get_ptr_vtable({:?})", ptr);
743+
let (alloc_id, offset, _tag) = self.ptr_get_alloc_id(ptr)?;
744+
if offset.bytes() != 0 {
745+
throw_ub!(InvalidVtablePointer(Pointer::new(alloc_id, offset)))
746+
}
747+
match self.tcx.try_get_global_alloc(alloc_id) {
748+
Some(GlobalAlloc::Vtable(ty, trait_ref)) => Ok((ty, trait_ref)),
749+
_ => throw_ub!(InvalidVtablePointer(Pointer::new(alloc_id, offset))),
750+
}
751+
}
752+
734753
pub fn alloc_mark_immutable(&mut self, id: AllocId) -> InterpResult<'tcx> {
735754
self.get_alloc_raw_mut(id)?.0.mutability = Mutability::Not;
736755
Ok(())

compiler/rustc_const_eval/src/interpret/place.rs

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -886,28 +886,19 @@ where
886886
}
887887

888888
/// Turn a place with a `dyn Trait` type into a place with the actual dynamic type.
889-
/// Also return some more information so drop doesn't have to run the same code twice.
890889
pub(super) fn unpack_dyn_trait(
891890
&self,
892891
mplace: &MPlaceTy<'tcx, M::PointerTag>,
893-
) -> InterpResult<'tcx, (ty::Instance<'tcx>, MPlaceTy<'tcx, M::PointerTag>)> {
892+
) -> InterpResult<'tcx, MPlaceTy<'tcx, M::PointerTag>> {
894893
let vtable = self.scalar_to_ptr(mplace.vtable())?; // also sanity checks the type
895-
let (instance, ty) = self.read_drop_type_from_vtable(vtable)?;
894+
let (ty, _) = self.get_ptr_vtable(vtable)?;
896895
let layout = self.layout_of(ty)?;
897896

898-
// More sanity checks
899-
if cfg!(debug_assertions) {
900-
let (size, align) = self.read_size_and_align_from_vtable(vtable)?;
901-
assert_eq!(size, layout.size);
902-
// only ABI alignment is preserved
903-
assert_eq!(align, layout.align.abi);
904-
}
905-
906897
let mplace = MPlaceTy {
907898
mplace: MemPlace { meta: MemPlaceMeta::None, ..**mplace },
908899
layout,
909900
align: layout.align.abi,
910901
};
911-
Ok((instance, mplace))
902+
Ok(mplace)
912903
}
913904
}

compiler/rustc_const_eval/src/interpret/terminator.rs

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
use std::borrow::Cow;
2-
use std::convert::TryFrom;
32

43
use rustc_middle::ty::layout::{FnAbiOf, LayoutOf};
54
use rustc_middle::ty::Instance;
@@ -563,7 +562,12 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
563562
ty::Dynamic(..)
564563
));
565564
let vtable = self.scalar_to_ptr(receiver_place.meta.unwrap_meta())?;
566-
let fn_val = self.get_vtable_slot(vtable, u64::try_from(idx).unwrap())?;
565+
let Some(ty::VtblEntry::Method(fn_inst)) = self.get_vtable_entries(vtable)?.get(idx).copied() else {
566+
throw_ub_format!(
567+
"calling index {idx} of vtable {vtable} but \
568+
that vtable is too small or does not have a method at that index"
569+
)
570+
};
567571

568572
// `*mut receiver_place.layout.ty` is almost the layout that we
569573
// want for args[0]: We have to project to field 0 because we want
@@ -579,7 +583,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
579583
trace!("Patched receiver operand to {:#?}", args[0]);
580584
// recurse with concrete function
581585
self.eval_fn_call(
582-
fn_val,
586+
FnVal::Instance(fn_inst),
583587
(caller_abi, caller_fn_abi),
584588
&args,
585589
with_caller_location,
@@ -606,8 +610,10 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
606610

607611
let (instance, place) = match place.layout.ty.kind() {
608612
ty::Dynamic(..) => {
609-
// Dropping a trait object.
610-
self.unpack_dyn_trait(&place)?
613+
// Dropping a trait object. Need to find actual drop fn.
614+
let place = self.unpack_dyn_trait(&place)?;
615+
let instance = ty::Instance::resolve_drop_in_place(*self.tcx, place.layout.ty);
616+
(instance, place)
611617
}
612618
_ => (instance, place),
613619
};
Lines changed: 24 additions & 109 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,10 @@
1-
use std::convert::TryFrom;
2-
3-
use rustc_middle::mir::interpret::{alloc_range, InterpResult, Pointer, PointerArithmetic};
4-
use rustc_middle::ty::{
5-
self, Ty, TyCtxt, COMMON_VTABLE_ENTRIES_ALIGN, COMMON_VTABLE_ENTRIES_DROPINPLACE,
6-
COMMON_VTABLE_ENTRIES_SIZE,
7-
};
1+
use rustc_middle::mir::interpret::{InterpResult, Pointer};
2+
use rustc_middle::ty::layout::LayoutOf;
3+
use rustc_middle::ty::{self, Ty, TyCtxt};
84
use rustc_target::abi::{Align, Size};
95

106
use super::util::ensure_monomorphic_enough;
11-
use super::{FnVal, InterpCx, Machine};
7+
use super::{InterpCx, Machine};
128

139
impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
1410
/// Creates a dynamic vtable for the given type and vtable origin. This is used only for
@@ -17,8 +13,8 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
1713
/// The `trait_ref` encodes the erased self type. Hence, if we are
1814
/// making an object `Foo<Trait>` from a value of type `Foo<T>`, then
1915
/// `trait_ref` would map `T: Trait`.
20-
pub fn get_vtable(
21-
&mut self,
16+
pub fn get_vtable_ptr(
17+
&self,
2218
ty: Ty<'tcx>,
2319
poly_trait_ref: Option<ty::PolyExistentialTraitRef<'tcx>>,
2420
) -> InterpResult<'tcx, Pointer<Option<M::PointerTag>>> {
@@ -30,114 +26,33 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
3026
ensure_monomorphic_enough(*self.tcx, ty)?;
3127
ensure_monomorphic_enough(*self.tcx, poly_trait_ref)?;
3228

33-
let vtable_allocation = self.tcx.vtable_allocation((ty, poly_trait_ref));
34-
35-
let vtable_ptr = self.global_base_pointer(Pointer::from(vtable_allocation))?;
36-
29+
let vtable_symbolic_allocation = self.tcx.create_vtable_alloc(ty, poly_trait_ref);
30+
let vtable_ptr = self.global_base_pointer(Pointer::from(vtable_symbolic_allocation))?;
3731
Ok(vtable_ptr.into())
3832
}
3933

40-
/// Resolves the function at the specified slot in the provided
41-
/// vtable. Currently an index of '3' (`TyCtxt::COMMON_VTABLE_ENTRIES.len()`)
42-
/// corresponds to the first method declared in the trait of the provided vtable.
43-
pub fn get_vtable_slot(
34+
/// Returns a high-level representation of the entires of the given vtable.
35+
pub fn get_vtable_entries(
4436
&self,
4537
vtable: Pointer<Option<M::PointerTag>>,
46-
idx: u64,
47-
) -> InterpResult<'tcx, FnVal<'tcx, M::ExtraFnVal>> {
48-
let ptr_size = self.pointer_size();
49-
let vtable_slot = vtable.offset(ptr_size * idx, self)?;
50-
let vtable_slot = self
51-
.get_ptr_alloc(vtable_slot, ptr_size, self.tcx.data_layout.pointer_align.abi)?
52-
.expect("cannot be a ZST");
53-
let fn_ptr = self.scalar_to_ptr(vtable_slot.read_pointer(Size::ZERO)?.check_init()?)?;
54-
self.get_ptr_fn(fn_ptr)
38+
) -> InterpResult<'tcx, &'tcx [ty::VtblEntry<'tcx>]> {
39+
let (ty, poly_trait_ref) = self.get_ptr_vtable(vtable)?;
40+
Ok(if let Some(poly_trait_ref) = poly_trait_ref {
41+
let trait_ref = poly_trait_ref.with_self_ty(*self.tcx, ty);
42+
let trait_ref = self.tcx.erase_regions(trait_ref);
43+
self.tcx.vtable_entries(trait_ref)
44+
} else {
45+
TyCtxt::COMMON_VTABLE_ENTRIES
46+
})
5547
}
5648

57-
/// Returns the drop fn instance as well as the actual dynamic type.
58-
pub fn read_drop_type_from_vtable(
59-
&self,
60-
vtable: Pointer<Option<M::PointerTag>>,
61-
) -> InterpResult<'tcx, (ty::Instance<'tcx>, Ty<'tcx>)> {
62-
let pointer_size = self.pointer_size();
63-
// We don't care about the pointee type; we just want a pointer.
64-
let vtable = self
65-
.get_ptr_alloc(
66-
vtable,
67-
pointer_size * u64::try_from(TyCtxt::COMMON_VTABLE_ENTRIES.len()).unwrap(),
68-
self.tcx.data_layout.pointer_align.abi,
69-
)?
70-
.expect("cannot be a ZST");
71-
let drop_fn = vtable
72-
.read_pointer(pointer_size * u64::try_from(COMMON_VTABLE_ENTRIES_DROPINPLACE).unwrap())?
73-
.check_init()?;
74-
// We *need* an instance here, no other kind of function value, to be able
75-
// to determine the type.
76-
let drop_instance = self.get_ptr_fn(self.scalar_to_ptr(drop_fn)?)?.as_instance()?;
77-
trace!("Found drop fn: {:?}", drop_instance);
78-
let fn_sig = drop_instance.ty(*self.tcx, self.param_env).fn_sig(*self.tcx);
79-
let fn_sig = self.tcx.normalize_erasing_late_bound_regions(self.param_env, fn_sig);
80-
// The drop function takes `*mut T` where `T` is the type being dropped, so get that.
81-
let args = fn_sig.inputs();
82-
if args.len() != 1 {
83-
throw_ub!(InvalidVtableDropFn(fn_sig));
84-
}
85-
let ty =
86-
args[0].builtin_deref(true).ok_or_else(|| err_ub!(InvalidVtableDropFn(fn_sig)))?.ty;
87-
Ok((drop_instance, ty))
88-
}
89-
90-
pub fn read_size_and_align_from_vtable(
49+
pub fn get_vtable_size_and_align(
9150
&self,
9251
vtable: Pointer<Option<M::PointerTag>>,
9352
) -> InterpResult<'tcx, (Size, Align)> {
94-
let pointer_size = self.pointer_size();
95-
// We check for `size = 3 * ptr_size`, which covers the drop fn (unused here),
96-
// the size, and the align (which we read below).
97-
let vtable = self
98-
.get_ptr_alloc(
99-
vtable,
100-
pointer_size * u64::try_from(TyCtxt::COMMON_VTABLE_ENTRIES.len()).unwrap(),
101-
self.tcx.data_layout.pointer_align.abi,
102-
)?
103-
.expect("cannot be a ZST");
104-
let size = vtable
105-
.read_integer(alloc_range(
106-
pointer_size * u64::try_from(COMMON_VTABLE_ENTRIES_SIZE).unwrap(),
107-
pointer_size,
108-
))?
109-
.check_init()?;
110-
let size = size.to_machine_usize(self)?;
111-
let size = Size::from_bytes(size);
112-
let align = vtable
113-
.read_integer(alloc_range(
114-
pointer_size * u64::try_from(COMMON_VTABLE_ENTRIES_ALIGN).unwrap(),
115-
pointer_size,
116-
))?
117-
.check_init()?;
118-
let align = align.to_machine_usize(self)?;
119-
let align = Align::from_bytes(align).map_err(|e| err_ub!(InvalidVtableAlignment(e)))?;
120-
121-
if size > self.max_size_of_val() {
122-
throw_ub!(InvalidVtableSize);
123-
}
124-
Ok((size, align))
125-
}
126-
127-
pub fn read_new_vtable_after_trait_upcasting_from_vtable(
128-
&self,
129-
vtable: Pointer<Option<M::PointerTag>>,
130-
idx: u64,
131-
) -> InterpResult<'tcx, Pointer<Option<M::PointerTag>>> {
132-
let pointer_size = self.pointer_size();
133-
134-
let vtable_slot = vtable.offset(pointer_size * idx, self)?;
135-
let new_vtable = self
136-
.get_ptr_alloc(vtable_slot, pointer_size, self.tcx.data_layout.pointer_align.abi)?
137-
.expect("cannot be a ZST");
138-
139-
let new_vtable = self.scalar_to_ptr(new_vtable.read_pointer(Size::ZERO)?.check_init()?)?;
140-
141-
Ok(new_vtable)
53+
let (ty, _trait_ref) = self.get_ptr_vtable(vtable)?;
54+
let layout = self.layout_of(ty)?;
55+
assert!(!layout.is_unsized(), "there are no vtables for unsized types");
56+
Ok((layout.size, layout.align.abi))
14257
}
14358
}

0 commit comments

Comments
 (0)