Skip to content

Commit 0aa6c41

Browse files
Support trait_upcasting (#4001)
This adds support for the recently stabilized `trait_upcasting` feature. Currently, when we cast between non-matching dyn traits, codegen incorrectly reuses the old vtable, leading to confusing issues (#3998). This reproduces the upcasting logic from upstream rustc to load the vptr when performing the coercion. Resolves #358 Resolves #3998 This was tested using the motivating example from the linked issue. I will add further tests to make sure this feature is robust. By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 and MIT licenses. --------- Co-authored-by: clubby789 <[email protected]>
1 parent 9de733a commit 0aa6c41

File tree

3 files changed

+134
-16
lines changed

3 files changed

+134
-16
lines changed

kani-compiler/src/codegen_cprover_gotoc/codegen/rvalue.rs

Lines changed: 54 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,10 @@ use stable_mir::mir::mono::Instance;
2626
use stable_mir::mir::{
2727
AggregateKind, BinOp, CastKind, NullOp, Operand, Place, PointerCoercion, Rvalue, UnOp,
2828
};
29-
use stable_mir::ty::{ClosureKind, IntTy, RigidTy, Size, Ty, TyConst, TyKind, UintTy, VariantIdx};
29+
use stable_mir::ty::{
30+
Binder, ClosureKind, ExistentialPredicate, IntTy, RigidTy, Size, Ty, TyConst, TyKind, UintTy,
31+
VariantIdx,
32+
};
3033
use std::collections::BTreeMap;
3134
use tracing::{debug, trace, warn};
3235

@@ -1528,9 +1531,26 @@ impl GotocCtx<'_> {
15281531
VtblEntry::MetadataSize => Some(vt_size.clone()),
15291532
VtblEntry::MetadataAlign => Some(vt_align.clone()),
15301533
VtblEntry::Vacant => None,
1531-
// TODO: trait upcasting
1532-
// https://github.com/model-checking/kani/issues/358
1533-
VtblEntry::TraitVPtr(_trait_ref) => None,
1534+
VtblEntry::TraitVPtr(trait_ref) => {
1535+
let projections = match dst_mir_type.kind() {
1536+
TyKind::RigidTy(RigidTy::Dynamic(predicates, ..)) => predicates
1537+
.iter()
1538+
.filter_map(|pred| match &pred.value {
1539+
ExistentialPredicate::Projection(proj) => {
1540+
Some(Binder::bind_with_vars(
1541+
proj.clone(),
1542+
pred.bound_vars.clone(),
1543+
))
1544+
}
1545+
_ => None,
1546+
})
1547+
.collect(),
1548+
_ => vec![],
1549+
};
1550+
1551+
let dyn_ref = ctx.trait_ref_to_dyn_trait(*trait_ref, projections);
1552+
Some(ctx.codegen_vtable(src_mir_type, dyn_ref, loc).address_of())
1553+
}
15341554
VtblEntry::Method(instance) => Some(ctx.codegen_vtable_method_field(
15351555
rustc_internal::stable(instance),
15361556
trait_type,
@@ -1593,18 +1613,44 @@ impl GotocCtx<'_> {
15931613
};
15941614
slice_fat_ptr(fat_ptr_type, dst_data_expr, dst_goto_len, &self.symbol_table)
15951615
}
1596-
(TyKind::RigidTy(RigidTy::Dynamic(..)), TyKind::RigidTy(RigidTy::Dynamic(..))) => {
1597-
// Cast between fat pointers. Cast the data and the source
1616+
(
1617+
ref src_ty @ TyKind::RigidTy(RigidTy::Dynamic(.., ref src_dyn_kind)),
1618+
ref dst_ty @ TyKind::RigidTy(RigidTy::Dynamic(.., ref dst_dyn_kind)),
1619+
) => {
1620+
assert_eq!(
1621+
src_dyn_kind, dst_dyn_kind,
1622+
"casting from `{src_dyn_kind:?}` to `{dst_dyn_kind:?}` is not supported"
1623+
);
1624+
// Cast between dyn pointers. Cast the data and the source
15981625
let src_data = src_goto_expr.to_owned().member("data", &self.symbol_table);
15991626
let dst_data = src_data.cast_to(dst_data_type);
16001627

16011628
// Retrieve the vtable and cast the vtable type.
16021629
let src_vtable = src_goto_expr.member("vtable", &self.symbol_table);
16031630
let vtable_name = self.vtable_name_stable(metadata_dst_type);
16041631
let vtable_ty = Type::struct_tag(vtable_name).to_pointer();
1605-
let dst_vtable = src_vtable.cast_to(vtable_ty);
16061632

1607-
// Construct a fat pointer with the same (casted) fields and new type
1633+
// Note: Logic based on upstream rustc_codegen_ssa:
1634+
// https://github.com/rust-lang/rust/blob/8bf5a8d/compiler/rustc_codegen_ssa/src/base.rs#L171
1635+
let dst_vtable = if src_ty.trait_principal() == dst_ty.trait_principal()
1636+
|| dst_ty.trait_principal().is_none()
1637+
{
1638+
// If we're casting to the same dyn trait, we can reuse the old vtable
1639+
src_vtable.cast_to(vtable_ty)
1640+
} else if let Some(vptr_entry_idx) = self.tcx.supertrait_vtable_slot((
1641+
rustc_internal::internal(self.tcx, metadata_src_type),
1642+
rustc_internal::internal(self.tcx, metadata_dst_type),
1643+
)) {
1644+
// Otherwise, if the principals differ and our vtable contains a pointer to the supertrait vtable,
1645+
// we can load that
1646+
src_vtable
1647+
.dereference()
1648+
.member(vptr_entry_idx.to_string(), &self.symbol_table)
1649+
.cast_to(vtable_ty)
1650+
} else {
1651+
// Fallback to original vtable
1652+
src_vtable.cast_to(vtable_ty)
1653+
};
16081654
dynamic_fat_ptr(fat_ptr_type, dst_data, dst_vtable, &self.symbol_table)
16091655
}
16101656
(_, TyKind::RigidTy(RigidTy::Dynamic(..))) => {

kani-compiler/src/codegen_cprover_gotoc/codegen/typ.rs

Lines changed: 51 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,20 +10,24 @@ use rustc_abi::{
1010
};
1111
use rustc_ast::ast::Mutability;
1212
use rustc_index::IndexVec;
13-
use rustc_middle::ty::GenericArgsRef;
1413
use rustc_middle::ty::layout::LayoutOf;
1514
use rustc_middle::ty::print::FmtPrinter;
1615
use rustc_middle::ty::print::with_no_trimmed_paths;
1716
use rustc_middle::ty::{
18-
self, AdtDef, Const, CoroutineArgs, CoroutineArgsExt, FloatTy, Instance, IntTy, PolyFnSig, Ty,
19-
TyCtxt, TyKind, UintTy, VariantDef, VtblEntry,
17+
self, AdtDef, Const, CoroutineArgs, CoroutineArgsExt, FloatTy, Instance, IntTy, PolyFnSig,
18+
TraitRef, Ty, TyCtxt, TyKind, UintTy, VariantDef, VtblEntry,
2019
};
20+
use rustc_middle::ty::{ExistentialTraitRef, GenericArgsRef};
2121
use rustc_middle::ty::{List, TypeFoldable};
2222
use rustc_smir::rustc_internal;
2323
use rustc_span::def_id::DefId;
2424
use stable_mir::abi::{ArgAbi, FnAbi, PassMode};
2525
use stable_mir::mir::Body;
2626
use stable_mir::mir::mono::Instance as InstanceStable;
27+
use stable_mir::ty::{
28+
Binder, DynKind, ExistentialPredicate, ExistentialProjection, Region, RegionKind, RigidTy,
29+
Ty as StableTy,
30+
};
2731
use tracing::{debug, trace, warn};
2832

2933
/// Map the unit type to an empty struct
@@ -254,8 +258,7 @@ impl<'tcx> GotocCtx<'tcx> {
254258
.is_sized(*self.tcx.at(rustc_span::DUMMY_SP), ty::TypingEnv::fully_monomorphized())
255259
}
256260

257-
/// Generates the type for a single field for a dynamic vtable.
258-
/// In particular, these fields are function pointers.
261+
/// Generates the type for a supertrait vtable pointer field for a vtable.
259262
fn trait_method_vtable_field_type(
260263
&mut self,
261264
instance: Instance<'tcx>,
@@ -274,6 +277,44 @@ impl<'tcx> GotocCtx<'tcx> {
274277
DatatypeComponent::field(vtable_field_name, fn_ptr)
275278
}
276279

280+
pub fn trait_ref_to_dyn_trait(
281+
&self,
282+
trait_ref: TraitRef<'tcx>,
283+
projections: Vec<Binder<ExistentialProjection>>,
284+
) -> StableTy {
285+
let existential_trait_ref =
286+
rustc_internal::stable(ExistentialTraitRef::erase_self_ty(self.tcx, trait_ref));
287+
let binder = Binder::dummy(ExistentialPredicate::Trait(existential_trait_ref));
288+
289+
let mut predictates = vec![binder];
290+
predictates.extend(
291+
projections.into_iter().map(|proj| proj.map_bound(ExistentialPredicate::Projection)),
292+
);
293+
let rigid =
294+
RigidTy::Dynamic(predictates, Region { kind: RegionKind::ReErased }, DynKind::Dyn);
295+
296+
stable_mir::ty::Ty::from_rigid_kind(rigid)
297+
}
298+
299+
/// Generates the type for a single field for a dynamic vtable.
300+
/// In particular, these fields are function pointers.
301+
fn trait_vptr_vtable_field_type(
302+
&mut self,
303+
idx: usize,
304+
trait_ref: TraitRef<'tcx>,
305+
projections: Vec<Binder<ExistentialProjection>>,
306+
) -> DatatypeComponent {
307+
let dyn_trait = self.trait_ref_to_dyn_trait(trait_ref, projections);
308+
let vtable_ty =
309+
self.codegen_trait_vtable_type(rustc_internal::internal(self.tcx, dyn_trait));
310+
let vtable_ptr = vtable_ty.to_pointer();
311+
312+
// vtable field name, i.e., 3_vol (idx_method)
313+
let vtable_field_name = self.vtable_field_name(idx);
314+
315+
DatatypeComponent::field(vtable_field_name, vtable_ptr)
316+
}
317+
277318
/// Generates a vtable that looks like this:
278319
/// struct io::error::vtable {
279320
/// void *drop_in_place;
@@ -383,9 +424,11 @@ impl<'tcx> GotocCtx<'tcx> {
383424
VtblEntry::Method(instance) => {
384425
Some(self.trait_method_vtable_field_type(instance, idx))
385426
}
386-
// TODO: trait upcasting
387-
// https://github.com/model-checking/kani/issues/358
388-
VtblEntry::TraitVPtr(..) => None,
427+
VtblEntry::TraitVPtr(trait_ref) => Some(self.trait_vptr_vtable_field_type(
428+
idx,
429+
trait_ref,
430+
binder.projection_bounds().map(rustc_internal::stable).collect(),
431+
)),
389432
VtblEntry::MetadataDropInPlace
390433
| VtblEntry::MetadataSize
391434
| VtblEntry::MetadataAlign

tests/kani/DynTrait/upcast.rs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
// Copyright Kani Contributors
2+
// SPDX-License-Identifier: Apache-2.0 OR MIT
3+
4+
//! Check that upcasting to a supertrait works correctly.
5+
6+
trait SubTrait: SuperTrait2 + SuperTrait1 {}
7+
8+
impl<T: SuperTrait1 + SuperTrait2> SubTrait for T {}
9+
10+
trait SuperTrait1 {
11+
fn trigger(&self, _old: &()) {}
12+
}
13+
14+
trait SuperTrait2 {
15+
#[allow(unused)]
16+
fn func(&self) {}
17+
}
18+
19+
#[derive(Clone, Copy, Default)]
20+
struct Struct;
21+
22+
impl SuperTrait1 for Struct {}
23+
impl SuperTrait2 for Struct {}
24+
25+
#[kani::proof]
26+
fn main() {
27+
let val: &dyn SubTrait = &Struct;
28+
(val as &dyn SuperTrait1).trigger(&());
29+
}

0 commit comments

Comments
 (0)