add a Vtable kind of symbolic allocations

This commit is contained in:
Ralf Jung 2022-07-17 11:36:37 -04:00
parent a7468c60f8
commit da5e4d73f1
11 changed files with 106 additions and 15 deletions

View File

@ -195,9 +195,13 @@ pub(crate) fn codegen_const_value<'tcx>(
}
Scalar::Ptr(ptr, _size) => {
let (alloc_id, offset) = ptr.into_parts(); // we know the `offset` is relative
let alloc_kind = fx.tcx.get_global_alloc(alloc_id);
let base_addr = match alloc_kind {
Some(GlobalAlloc::Memory(alloc)) => {
// For vtables, get the underlying data allocation.
let alloc_id = match fx.tcx.global_alloc(alloc_id) {
GlobalAlloc::Vtable(ty, trait_ref) => fx.tcx.vtable_allocation((ty, trait_ref)),
_ => alloc_id,
};
let base_addr = match fx.tcx.global_alloc(alloc_id) {
GlobalAlloc::Memory(alloc) => {
let data_id = data_id_for_alloc_id(
&mut fx.constants_cx,
fx.module,
@ -211,13 +215,14 @@ pub(crate) fn codegen_const_value<'tcx>(
}
fx.bcx.ins().global_value(fx.pointer_type, local_data_id)
}
Some(GlobalAlloc::Function(instance)) => {
GlobalAlloc::Function(instance) => {
let func_id = crate::abi::import_function(fx.tcx, fx.module, instance);
let local_func_id =
fx.module.declare_func_in_func(func_id, &mut fx.bcx.func);
fx.bcx.ins().func_addr(fx.pointer_type, local_func_id)
}
Some(GlobalAlloc::Static(def_id)) => {
GlobalAlloc::Vtable(..) => bug!("vtables are already handled"),
GlobalAlloc::Static(def_id) => {
assert!(fx.tcx.is_static(def_id));
let data_id = data_id_for_static(fx.tcx, fx.module, def_id, false);
let local_data_id =
@ -227,7 +232,6 @@ pub(crate) fn codegen_const_value<'tcx>(
}
fx.bcx.ins().global_value(fx.pointer_type, local_data_id)
}
None => bug!("missing allocation {:?}", alloc_id),
};
let val = if offset.bytes() != 0 {
fx.bcx.ins().iadd_imm(base_addr, i64::try_from(offset.bytes()).unwrap())
@ -360,7 +364,9 @@ fn define_all_allocs(tcx: TyCtxt<'_>, module: &mut dyn Module, cx: &mut Constant
//println!("alloc_id {}", alloc_id);
let alloc = match tcx.get_global_alloc(alloc_id).unwrap() {
GlobalAlloc::Memory(alloc) => alloc,
GlobalAlloc::Function(_) | GlobalAlloc::Static(_) => unreachable!(),
GlobalAlloc::Function(_) | GlobalAlloc::Static(_) | GlobalAlloc::Vtable(..) => {
unreachable!()
}
};
let data_id = *cx.anon_allocs.entry(alloc_id).or_insert_with(|| {
module
@ -424,7 +430,7 @@ fn define_all_allocs(tcx: TyCtxt<'_>, module: &mut dyn Module, cx: &mut Constant
read_target_uint(endianness, bytes).unwrap()
};
let reloc_target_alloc = tcx.get_global_alloc(alloc_id).unwrap();
let reloc_target_alloc = tcx.global_alloc(alloc_id);
let data_id = match reloc_target_alloc {
GlobalAlloc::Function(instance) => {
assert_eq!(addend, 0);
@ -436,6 +442,10 @@ fn define_all_allocs(tcx: TyCtxt<'_>, module: &mut dyn Module, cx: &mut Constant
GlobalAlloc::Memory(target_alloc) => {
data_id_for_alloc_id(cx, module, alloc_id, target_alloc.inner().mutability)
}
GlobalAlloc::Vtable(ty, trait_ref) => {
let alloc_id = tcx.vtable_allocation((ty, trait_ref));
data_id_for_alloc_id(cx, module, alloc_id, Mutability::Not)
}
GlobalAlloc::Static(def_id) => {
if tcx.codegen_fn_attrs(def_id).flags.contains(CodegenFnAttrFlags::THREAD_LOCAL)
{

View File

@ -183,6 +183,13 @@ fn scalar_to_backend(&self, cv: Scalar, layout: abi::Scalar, ty: Type<'gcc>) ->
}
Scalar::Ptr(ptr, _size) => {
let (alloc_id, offset) = ptr.into_parts();
// For vtables, get the underlying data allocation.
let alloc_id = match self.tcx.global_alloc(alloc_id) {
GlobalAlloc::Vtable(ty, trait_ref) => {
self.tcx.vtable_allocation((ty, trait_ref))
}
_ => alloc_id,
};
let base_addr =
match self.tcx.global_alloc(alloc_id) {
GlobalAlloc::Memory(alloc) => {
@ -201,6 +208,7 @@ fn scalar_to_backend(&self, cv: Scalar, layout: abi::Scalar, ty: Type<'gcc>) ->
GlobalAlloc::Function(fn_instance) => {
self.get_fn_addr(fn_instance)
},
GlobalAlloc::Vtable(..) => panic!("vtables are already handled"),
GlobalAlloc::Static(def_id) => {
assert!(self.tcx.is_static(def_id));
self.get_static(def_id).get_address(None)

View File

@ -240,6 +240,13 @@ fn scalar_to_backend(&self, cv: Scalar, layout: abi::Scalar, llty: &'ll Type) ->
}
Scalar::Ptr(ptr, _size) => {
let (alloc_id, offset) = ptr.into_parts();
// For vtables, get the underlying data allocation.
let alloc_id = match self.tcx.global_alloc(alloc_id) {
GlobalAlloc::Vtable(ty, trait_ref) => {
self.tcx.vtable_allocation((ty, trait_ref))
}
_ => alloc_id,
};
let (base_addr, base_addr_space) = match self.tcx.global_alloc(alloc_id) {
GlobalAlloc::Memory(alloc) => {
let init = const_alloc_to_llvm(self, alloc);
@ -257,6 +264,7 @@ fn scalar_to_backend(&self, cv: Scalar, layout: abi::Scalar, llty: &'ll Type) ->
self.get_fn_addr(fn_instance.polymorphize(self.tcx)),
self.data_layout().instruction_address_space,
),
GlobalAlloc::Vtable(..) => bug!("vtables are already handled"),
GlobalAlloc::Static(def_id) => {
assert!(self.tcx.is_static(def_id));
assert!(!self.tcx.is_thread_local_static(def_id));

View File

@ -101,7 +101,9 @@ fn append_chunks_of_init_and_uninit_bytes<'ll, 'a, 'b>(
let address_space = match cx.tcx.global_alloc(alloc_id) {
GlobalAlloc::Function(..) => cx.data_layout().instruction_address_space,
GlobalAlloc::Static(..) | GlobalAlloc::Memory(..) => AddressSpace::DATA,
GlobalAlloc::Static(..) | GlobalAlloc::Memory(..) | GlobalAlloc::Vtable(..) => {
AddressSpace::DATA
}
};
llvals.push(cx.scalar_to_backend(

View File

@ -62,6 +62,8 @@ pub enum AllocKind {
LiveData,
/// A function allocation (that fn ptrs point to).
Function,
/// A (symbolic) vtable allocation.
Vtable,
/// A dead allocation.
Dead,
}
@ -291,6 +293,9 @@ pub fn deallocate_ptr(
Some(GlobalAlloc::Function(..)) => {
err_ub_format!("deallocating {alloc_id:?}, which is a function")
}
Some(GlobalAlloc::Vtable(..)) => {
err_ub_format!("deallocating {alloc_id:?}, which is a vtable")
}
Some(GlobalAlloc::Static(..) | GlobalAlloc::Memory(..)) => {
err_ub_format!("deallocating {alloc_id:?}, which is static memory")
}
@ -479,6 +484,7 @@ fn get_global_alloc(
(mem, None)
}
Some(GlobalAlloc::Function(..)) => throw_ub!(DerefFunctionPointer(id)),
Some(GlobalAlloc::Vtable(..)) => throw_ub!(DerefVtablePointer(id)),
None => throw_ub!(PointerUseAfterFree(id)),
Some(GlobalAlloc::Static(def_id)) => {
assert!(self.tcx.is_static(def_id));
@ -678,6 +684,10 @@ pub fn get_alloc_info(&self, id: AllocId) -> (Size, Align, AllocKind) {
(alloc.size(), alloc.align, AllocKind::LiveData)
}
Some(GlobalAlloc::Function(_)) => bug!("We already checked function pointers above"),
Some(GlobalAlloc::Vtable(..)) => {
// No data to be accessed here.
return (Size::ZERO, Align::ONE, AllocKind::Vtable);
}
// The rest must be dead.
None => {
// Deallocated pointers are allowed, we should be able to find
@ -840,7 +850,13 @@ fn write_allocation_track_relocs<'tcx, Prov: Provenance, Extra>(
)?;
}
Some(GlobalAlloc::Function(func)) => {
write!(fmt, " (fn: {})", func)?;
write!(fmt, " (fn: {func})")?;
}
Some(GlobalAlloc::Vtable(ty, Some(trait_ref))) => {
write!(fmt, " (vtable: impl {trait_ref} for {ty})")?;
}
Some(GlobalAlloc::Vtable(ty, None)) => {
write!(fmt, " (vtable: impl ? for {ty})")?;
}
Some(GlobalAlloc::Static(did)) => {
write!(fmt, " (static: {})", self.ecx.tcx.def_path_str(did))?;

View File

@ -271,6 +271,8 @@ pub enum UndefinedBehaviorInfo<'tcx> {
WriteToReadOnly(AllocId),
// Trying to access the data behind a function pointer.
DerefFunctionPointer(AllocId),
// Trying to access the data behind a vtable pointer.
DerefVtablePointer(AllocId),
/// The value validity check found a problem.
/// Should only be thrown by `validity.rs` and always point out which part of the value
/// is the problem.
@ -359,6 +361,7 @@ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
),
WriteToReadOnly(a) => write!(f, "writing to {a:?} which is read-only"),
DerefFunctionPointer(a) => write!(f, "accessing {a:?} which contains a function"),
DerefVtablePointer(a) => write!(f, "accessing {a:?} which contains a vtable"),
ValidationFailure { path: None, msg } => {
write!(f, "constructing invalid value: {msg}")
}

View File

@ -196,6 +196,7 @@ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
enum AllocDiscriminant {
Alloc,
Fn,
Vtable,
Static,
}
@ -215,6 +216,12 @@ pub fn specialized_encode_alloc_id<'tcx, E: TyEncoder<I = TyCtxt<'tcx>>>(
AllocDiscriminant::Fn.encode(encoder);
fn_instance.encode(encoder);
}
GlobalAlloc::Vtable(ty, poly_trait_ref) => {
trace!("encoding {:?} with {ty:#?}, {poly_trait_ref:#?}", alloc_id);
AllocDiscriminant::Vtable.encode(encoder);
ty.encode(encoder);
poly_trait_ref.encode(encoder);
}
GlobalAlloc::Static(did) => {
assert!(!tcx.is_thread_local_static(did));
// References to statics doesn't need to know about their allocations,
@ -305,7 +312,7 @@ pub fn decode_alloc_id<'tcx, D>(&self, decoder: &mut D) -> AllocId
State::InProgress(TinyList::new_single(self.session_id), alloc_id);
Some(alloc_id)
}
AllocDiscriminant::Fn | AllocDiscriminant::Static => {
AllocDiscriminant::Fn | AllocDiscriminant::Static | AllocDiscriminant::Vtable => {
// Fns and statics cannot be cyclic, and their `AllocId`
// is determined later by interning.
*entry =
@ -355,6 +362,15 @@ pub fn decode_alloc_id<'tcx, D>(&self, decoder: &mut D) -> AllocId
let alloc_id = decoder.interner().create_fn_alloc(instance);
alloc_id
}
AllocDiscriminant::Vtable => {
assert!(alloc_id.is_none());
trace!("creating static alloc ID");
let ty = <Ty<'_> as Decodable<D>>::decode(decoder);
let poly_trait_ref = <Option<ty::PolyExistentialTraitRef<'_>> as Decodable<D>>::decode(decoder);
trace!("decoded vtable alloc instance: {ty:?}, {poly_trait_ref:?}");
let alloc_id = decoder.interner().create_vtable_alloc(ty, poly_trait_ref);
alloc_id
}
AllocDiscriminant::Static => {
assert!(alloc_id.is_none());
trace!("creating extern static alloc ID");
@ -380,6 +396,8 @@ pub fn decode_alloc_id<'tcx, D>(&self, decoder: &mut D) -> AllocId
pub enum GlobalAlloc<'tcx> {
/// The alloc ID is used as a function pointer.
Function(Instance<'tcx>),
/// This alloc ID points to a symbolic (not-reified) vtable.
Vtable(Ty<'tcx>, Option<ty::PolyExistentialTraitRef<'tcx>>),
/// The alloc ID points to a "lazy" static variable that did not get computed (yet).
/// This is also used to break the cycle in recursive statics.
Static(DefId),
@ -407,6 +425,16 @@ pub fn unwrap_fn(&self) -> Instance<'tcx> {
_ => bug!("expected function, got {:?}", self),
}
}
/// Panics if the `GlobalAlloc` is not `GlobalAlloc::Vtable`
#[track_caller]
#[inline]
pub fn unwrap_vtable(&self) -> (Ty<'tcx>, Option<ty::PolyExistentialTraitRef<'tcx>>) {
match *self {
GlobalAlloc::Vtable(ty, poly_trait_ref) => (ty, poly_trait_ref),
_ => bug!("expected vtable, got {:?}", self),
}
}
}
pub(crate) struct AllocMap<'tcx> {
@ -454,12 +482,12 @@ pub fn reserve_alloc_id(self) -> AllocId {
}
/// Reserves a new ID *if* this allocation has not been dedup-reserved before.
/// Should only be used for function pointers and statics, we don't want
/// to dedup IDs for "real" memory!
/// Should only be used for "symbolic" allocations (function pointers, vtables, statics), we
/// don't want to dedup IDs for "real" memory!
fn reserve_and_set_dedup(self, alloc: GlobalAlloc<'tcx>) -> AllocId {
let mut alloc_map = self.alloc_map.lock();
match alloc {
GlobalAlloc::Function(..) | GlobalAlloc::Static(..) => {}
GlobalAlloc::Function(..) | GlobalAlloc::Static(..) | GlobalAlloc::Vtable(..) => {}
GlobalAlloc::Memory(..) => bug!("Trying to dedup-reserve memory with real data!"),
}
if let Some(&alloc_id) = alloc_map.dedup.get(&alloc) {
@ -504,6 +532,11 @@ pub fn create_fn_alloc(self, instance: Instance<'tcx>) -> AllocId {
}
}
/// Generates an `AllocId` for a (symbolic, not-reified) vtable. Will get deduplicated.
pub fn create_vtable_alloc(self, ty: Ty<'tcx>, poly_trait_ref: Option<ty::PolyExistentialTraitRef<'tcx>>) -> AllocId {
self.reserve_and_set_dedup(GlobalAlloc::Vtable(ty, poly_trait_ref))
}
/// Interns the `Allocation` and return a new `AllocId`, even if there's already an identical
/// `Allocation` with a different `AllocId`.
/// Statics with identical content will still point to the same `Allocation`, i.e.,

View File

@ -725,6 +725,8 @@ fn visit_constant(&mut self, c: &Constant<'tcx>, loc: Location) {
// gracefully handle it and allow buggy rustc to be debugged via allocation printing.
None => write!(w, " (deallocated)")?,
Some(GlobalAlloc::Function(inst)) => write!(w, " (fn: {inst})")?,
Some(GlobalAlloc::Vtable(ty, Some(trait_ref))) => write!(w, " (vtable: impl {trait_ref} for {ty})")?,
Some(GlobalAlloc::Vtable(ty, None)) => write!(w, " (vtable: impl ? for {ty})")?,
Some(GlobalAlloc::Static(did)) if !tcx.is_foreign_item(did) => {
match tcx.eval_static_initializer(did) {
Ok(alloc) => {

View File

@ -523,4 +523,5 @@ fn decode(decoder: &mut D) -> Self {
ty::ExistentialPredicate<'tcx>,
ty::TraitRef<'tcx>,
Vec<ty::GeneratorInteriorTypeCause<'tcx>>,
ty::ExistentialTraitRef<'tcx>,
}

View File

@ -1282,11 +1282,12 @@ fn pretty_print_const_scalar_ptr(
p!("<too short allocation>")
}
}
// FIXME: for statics and functions, we could in principle print more detail.
// FIXME: for statics, vtables, and functions, we could in principle print more detail.
Some(GlobalAlloc::Static(def_id)) => {
p!(write("<static({:?})>", def_id))
}
Some(GlobalAlloc::Function(_)) => p!("<function>"),
Some(GlobalAlloc::Vtable(..)) => p!("<vtable>"),
None => p!("<dangling pointer>"),
}
return Ok(self);

View File

@ -1427,6 +1427,13 @@ fn collect_miri<'tcx>(tcx: TyCtxt<'tcx>, alloc_id: AllocId, output: &mut MonoIte
output.push(create_fn_mono_item(tcx, fn_instance, DUMMY_SP));
}
}
GlobalAlloc::Vtable(ty, trait_ref) => {
// FIXME(RJ) no ideas if this is correct. There is this nice
// `create_mono_items_for_vtable_methods` method but I wouldn't know how to call it from
// here. So instead we just generate the actual vtable and recurse.
let alloc_id = tcx.vtable_allocation((ty, trait_ref));
collect_miri(tcx, alloc_id, output)
}
}
}