Skip to content
This repository was archived by the owner on May 28, 2025. It is now read-only.

Commit fe00573

Browse files
committed
make use of symbolic vtables in interpreter
1 parent a10d8e4 commit fe00573

23 files changed

+283
-377
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
@@ -631,7 +631,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
631631
ty::Dynamic(..) => {
632632
let vtable = self.scalar_to_ptr(metadata.unwrap_meta())?;
633633
// Read size and align from vtable (already checks size).
634-
Ok(Some(self.read_size_and_align_from_vtable(vtable)?))
634+
Ok(Some(self.get_vtable_size_and_align(vtable)?))
635635
}
636636

637637
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::{
@@ -500,6 +500,8 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
500500
// contains a reference to memory that was created during its evaluation (i.e., not
501501
// to another static), those inner references only exist in "resolved" form.
502502
if self.tcx.is_foreign_item(def_id) {
503+
// This is unreachable in Miri, but can happen in CTFE where we actually *do* support
504+
// referencing arbitrary (declared) extern statics.
503505
throw_unsup!(ReadExternStatic(def_id));
504506
}
505507

@@ -670,11 +672,13 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
670672
// Can't do this in the match argument, we may get cycle errors since the lock would
671673
// be held throughout the match.
672674
match self.tcx.try_get_global_alloc(id) {
673-
Some(GlobalAlloc::Static(did)) => {
674-
assert!(!self.tcx.is_thread_local_static(did));
675+
Some(GlobalAlloc::Static(def_id)) => {
676+
assert!(self.tcx.is_static(def_id));
677+
assert!(!self.tcx.is_thread_local_static(def_id));
675678
// Use size and align of the type.
676-
let ty = self.tcx.type_of(did);
679+
let ty = self.tcx.type_of(def_id);
677680
let layout = self.tcx.layout_of(ParamEnv::empty().and(ty)).unwrap();
681+
assert!(!layout.is_unsized());
678682
(layout.size, layout.align.abi, AllocKind::LiveData)
679683
}
680684
Some(GlobalAlloc::Memory(alloc)) => {
@@ -685,8 +689,8 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
685689
}
686690
Some(GlobalAlloc::Function(_)) => bug!("We already checked function pointers above"),
687691
Some(GlobalAlloc::Vtable(..)) => {
688-
// No data to be accessed here.
689-
return (Size::ZERO, Align::ONE, AllocKind::Vtable);
692+
// No data to be accessed here. But vtables are pointer-aligned.
693+
return (Size::ZERO, self.tcx.data_layout.pointer_align.abi, AllocKind::Vtable);
690694
}
691695
// The rest must be dead.
692696
None => {
@@ -726,7 +730,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
726730
&self,
727731
ptr: Pointer<Option<M::Provenance>>,
728732
) -> InterpResult<'tcx, FnVal<'tcx, M::ExtraFnVal>> {
729-
trace!("get_fn({:?})", ptr);
733+
trace!("get_ptr_fn({:?})", ptr);
730734
let (alloc_id, offset, _prov) = self.ptr_get_alloc_id(ptr)?;
731735
if offset.bytes() != 0 {
732736
throw_ub!(InvalidFunctionPointer(Pointer::new(alloc_id, offset)))
@@ -735,6 +739,21 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
735739
.ok_or_else(|| err_ub!(InvalidFunctionPointer(Pointer::new(alloc_id, offset))).into())
736740
}
737741

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

compiler/rustc_const_eval/src/interpret/place.rs

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

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

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

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::Provenance>>> {
@@ -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::Provenance>>,
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::Provenance>>,
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::Provenance>>,
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::Provenance>>,
130-
idx: u64,
131-
) -> InterpResult<'tcx, Pointer<Option<M::Provenance>>> {
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)