Skip to content

Commit 44995f7

Browse files
committed
Auto merge of #89619 - michaelwoerister:incr-vtables, r=nagisa
Turn vtable_allocation() into a query This PR removes the untracked vtable-const-allocation cache from the `tcx` and turns the `vtable_allocation()` method into a query. The change is pretty straightforward and should be backportable without too much effort. Fixes #89598.
2 parents 3013b26 + b7cc991 commit 44995f7

File tree

10 files changed

+125
-82
lines changed

10 files changed

+125
-82
lines changed

compiler/rustc_codegen_cranelift/src/vtable.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ pub(crate) fn get_vtable<'tcx>(
6868
ty: Ty<'tcx>,
6969
trait_ref: Option<ty::PolyExistentialTraitRef<'tcx>>,
7070
) -> Value {
71-
let alloc_id = fx.tcx.vtable_allocation(ty, trait_ref);
71+
let alloc_id = fx.tcx.vtable_allocation((ty, trait_ref));
7272
let data_id =
7373
data_id_for_alloc_id(&mut fx.constants_cx, &mut *fx.module, alloc_id, Mutability::Not);
7474
let local_data_id = fx.module.declare_data_in_func(data_id, &mut fx.bcx.func);

compiler/rustc_codegen_ssa/src/meth.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ pub fn get_vtable<'tcx, Cx: CodegenMethods<'tcx>>(
7272
return val;
7373
}
7474

75-
let vtable_alloc_id = tcx.vtable_allocation(ty, trait_ref);
75+
let vtable_alloc_id = tcx.vtable_allocation((ty, trait_ref));
7676
let vtable_allocation = tcx.global_alloc(vtable_alloc_id).unwrap_memory();
7777
let vtable_const = cx.const_data_from_alloc(vtable_allocation);
7878
let align = cx.data_layout().pointer_align.abi;

compiler/rustc_const_eval/src/interpret/traits.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
3030
ensure_monomorphic_enough(*self.tcx, ty)?;
3131
ensure_monomorphic_enough(*self.tcx, poly_trait_ref)?;
3232

33-
let vtable_allocation = self.tcx.vtable_allocation(ty, poly_trait_ref);
33+
let vtable_allocation = self.tcx.vtable_allocation((ty, poly_trait_ref));
3434

3535
let vtable_ptr = self.memory.global_base_pointer(Pointer::from(vtable_allocation))?;
3636

compiler/rustc_middle/src/query/mod.rs

+7
Original file line numberDiff line numberDiff line change
@@ -1012,6 +1012,13 @@ rustc_queries! {
10121012
key.1, key.0 }
10131013
}
10141014

1015+
query vtable_allocation(key: (Ty<'tcx>, Option<ty::PolyExistentialTraitRef<'tcx>>)) -> mir::interpret::AllocId {
1016+
desc { |tcx| "vtable const allocation for <{} as {}>",
1017+
key.0,
1018+
key.1.map(|trait_ref| format!("{}", trait_ref)).unwrap_or("_".to_owned())
1019+
}
1020+
}
1021+
10151022
query codegen_fulfill_obligation(
10161023
key: (ty::ParamEnv<'tcx>, ty::PolyTraitRef<'tcx>)
10171024
) -> Result<ImplSource<'tcx, ()>, ErrorReported> {

compiler/rustc_middle/src/ty/context.rs

+1-6
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use crate::lint::{struct_lint_level, LintDiagnosticBuilder, LintLevelSource};
88
use crate::middle;
99
use crate::middle::resolve_lifetime::{self, LifetimeScopeForPath, ObjectLifetimeDefault};
1010
use crate::middle::stability;
11-
use crate::mir::interpret::{self, AllocId, Allocation, ConstValue, Scalar};
11+
use crate::mir::interpret::{self, Allocation, ConstValue, Scalar};
1212
use crate::mir::{Body, Field, Local, Place, PlaceElem, ProjectionKind, Promoted};
1313
use crate::thir::Thir;
1414
use crate::traits;
@@ -1047,10 +1047,6 @@ pub struct GlobalCtxt<'tcx> {
10471047
pub(crate) alloc_map: Lock<interpret::AllocMap<'tcx>>,
10481048

10491049
output_filenames: Arc<OutputFilenames>,
1050-
1051-
// FIXME(eddyb) this doesn't belong here and should be using a query.
1052-
pub(super) vtables_cache:
1053-
Lock<FxHashMap<(Ty<'tcx>, Option<ty::PolyExistentialTraitRef<'tcx>>), AllocId>>,
10541050
}
10551051

10561052
impl<'tcx> TyCtxt<'tcx> {
@@ -1189,7 +1185,6 @@ impl<'tcx> TyCtxt<'tcx> {
11891185
const_stability_interner: Default::default(),
11901186
alloc_map: Lock::new(interpret::AllocMap::new()),
11911187
output_filenames: Arc::new(output_filenames),
1192-
vtables_cache: Default::default(),
11931188
}
11941189
}
11951190

compiler/rustc_middle/src/ty/mod.rs

+1
Original file line numberDiff line numberDiff line change
@@ -2051,6 +2051,7 @@ pub fn provide(providers: &mut ty::query::Providers) {
20512051
trait_impls_of: trait_def::trait_impls_of_provider,
20522052
type_uninhabited_from: inhabitedness::type_uninhabited_from,
20532053
const_param_default: consts::const_param_default,
2054+
vtable_allocation: vtable::vtable_allocation_provider,
20542055
..*providers
20552056
};
20562057
}

compiler/rustc_middle/src/ty/print/pretty.rs

+1
Original file line numberDiff line numberDiff line change
@@ -2208,6 +2208,7 @@ forward_display_to_print! {
22082208
// because `for<'tcx>` isn't possible yet.
22092209
ty::Binder<'tcx, ty::ExistentialPredicate<'tcx>>,
22102210
ty::Binder<'tcx, ty::TraitRef<'tcx>>,
2211+
ty::Binder<'tcx, ty::ExistentialTraitRef<'tcx>>,
22112212
ty::Binder<'tcx, TraitRefPrintOnlyTraitPath<'tcx>>,
22122213
ty::Binder<'tcx, TraitRefPrintOnlyTraitName<'tcx>>,
22132214
ty::Binder<'tcx, ty::FnSig<'tcx>>,

compiler/rustc_middle/src/ty/vtable.rs

+60-73
Original file line numberDiff line numberDiff line change
@@ -43,85 +43,72 @@ pub const COMMON_VTABLE_ENTRIES_DROPINPLACE: usize = 0;
4343
pub const COMMON_VTABLE_ENTRIES_SIZE: usize = 1;
4444
pub const COMMON_VTABLE_ENTRIES_ALIGN: usize = 2;
4545

46-
impl<'tcx> TyCtxt<'tcx> {
47-
/// Retrieves an allocation that represents the contents of a vtable.
48-
/// There's a cache within `TyCtxt` so it will be deduplicated.
49-
pub fn vtable_allocation(
50-
self,
51-
ty: Ty<'tcx>,
52-
poly_trait_ref: Option<ty::PolyExistentialTraitRef<'tcx>>,
53-
) -> AllocId {
54-
let tcx = self;
55-
let vtables_cache = tcx.vtables_cache.lock();
56-
if let Some(alloc_id) = vtables_cache.get(&(ty, poly_trait_ref)).cloned() {
57-
return alloc_id;
58-
}
59-
drop(vtables_cache);
60-
61-
let vtable_entries = if let Some(poly_trait_ref) = poly_trait_ref {
62-
let trait_ref = poly_trait_ref.with_self_ty(tcx, ty);
63-
let trait_ref = tcx.erase_regions(trait_ref);
46+
/// Retrieves an allocation that represents the contents of a vtable.
47+
/// Since this is a query, allocations are cached and not duplicated.
48+
pub(super) fn vtable_allocation_provider<'tcx>(
49+
tcx: TyCtxt<'tcx>,
50+
key: (Ty<'tcx>, Option<ty::PolyExistentialTraitRef<'tcx>>),
51+
) -> AllocId {
52+
let (ty, poly_trait_ref) = key;
6453

65-
tcx.vtable_entries(trait_ref)
66-
} else {
67-
COMMON_VTABLE_ENTRIES
68-
};
54+
let vtable_entries = if let Some(poly_trait_ref) = poly_trait_ref {
55+
let trait_ref = poly_trait_ref.with_self_ty(tcx, ty);
56+
let trait_ref = tcx.erase_regions(trait_ref);
6957

70-
let layout = tcx
71-
.layout_of(ty::ParamEnv::reveal_all().and(ty))
72-
.expect("failed to build vtable representation");
73-
assert!(!layout.is_unsized(), "can't create a vtable for an unsized type");
74-
let size = layout.size.bytes();
75-
let align = layout.align.abi.bytes();
58+
tcx.vtable_entries(trait_ref)
59+
} else {
60+
COMMON_VTABLE_ENTRIES
61+
};
7662

77-
let ptr_size = tcx.data_layout.pointer_size;
78-
let ptr_align = tcx.data_layout.pointer_align.abi;
63+
let layout = tcx
64+
.layout_of(ty::ParamEnv::reveal_all().and(ty))
65+
.expect("failed to build vtable representation");
66+
assert!(!layout.is_unsized(), "can't create a vtable for an unsized type");
67+
let size = layout.size.bytes();
68+
let align = layout.align.abi.bytes();
7969

80-
let vtable_size = ptr_size * u64::try_from(vtable_entries.len()).unwrap();
81-
let mut vtable =
82-
Allocation::uninit(vtable_size, ptr_align, /* panic_on_fail */ true).unwrap();
70+
let ptr_size = tcx.data_layout.pointer_size;
71+
let ptr_align = tcx.data_layout.pointer_align.abi;
8372

84-
// No need to do any alignment checks on the memory accesses below, because we know the
85-
// allocation is correctly aligned as we created it above. Also we're only offsetting by
86-
// multiples of `ptr_align`, which means that it will stay aligned to `ptr_align`.
73+
let vtable_size = ptr_size * u64::try_from(vtable_entries.len()).unwrap();
74+
let mut vtable = Allocation::uninit(vtable_size, ptr_align, /* panic_on_fail */ true).unwrap();
8775

88-
for (idx, entry) in vtable_entries.iter().enumerate() {
89-
let idx: u64 = u64::try_from(idx).unwrap();
90-
let scalar = match entry {
91-
VtblEntry::MetadataDropInPlace => {
92-
let instance = ty::Instance::resolve_drop_in_place(tcx, ty);
93-
let fn_alloc_id = tcx.create_fn_alloc(instance);
94-
let fn_ptr = Pointer::from(fn_alloc_id);
95-
ScalarMaybeUninit::from_pointer(fn_ptr, &tcx)
96-
}
97-
VtblEntry::MetadataSize => Scalar::from_uint(size, ptr_size).into(),
98-
VtblEntry::MetadataAlign => Scalar::from_uint(align, ptr_size).into(),
99-
VtblEntry::Vacant => continue,
100-
VtblEntry::Method(instance) => {
101-
// Prepare the fn ptr we write into the vtable.
102-
let instance = instance.polymorphize(tcx);
103-
let fn_alloc_id = tcx.create_fn_alloc(instance);
104-
let fn_ptr = Pointer::from(fn_alloc_id);
105-
ScalarMaybeUninit::from_pointer(fn_ptr, &tcx)
106-
}
107-
VtblEntry::TraitVPtr(trait_ref) => {
108-
let super_trait_ref = trait_ref.map_bound(|trait_ref| {
109-
ty::ExistentialTraitRef::erase_self_ty(tcx, trait_ref)
110-
});
111-
let supertrait_alloc_id = self.vtable_allocation(ty, Some(super_trait_ref));
112-
let vptr = Pointer::from(supertrait_alloc_id);
113-
ScalarMaybeUninit::from_pointer(vptr, &tcx)
114-
}
115-
};
116-
vtable
117-
.write_scalar(&tcx, alloc_range(ptr_size * idx, ptr_size), scalar)
118-
.expect("failed to build vtable representation");
119-
}
76+
// No need to do any alignment checks on the memory accesses below, because we know the
77+
// allocation is correctly aligned as we created it above. Also we're only offsetting by
78+
// multiples of `ptr_align`, which means that it will stay aligned to `ptr_align`.
12079

121-
vtable.mutability = Mutability::Not;
122-
let alloc_id = tcx.create_memory_alloc(tcx.intern_const_alloc(vtable));
123-
let mut vtables_cache = self.vtables_cache.lock();
124-
vtables_cache.insert((ty, poly_trait_ref), alloc_id);
125-
alloc_id
80+
for (idx, entry) in vtable_entries.iter().enumerate() {
81+
let idx: u64 = u64::try_from(idx).unwrap();
82+
let scalar = match entry {
83+
VtblEntry::MetadataDropInPlace => {
84+
let instance = ty::Instance::resolve_drop_in_place(tcx, ty);
85+
let fn_alloc_id = tcx.create_fn_alloc(instance);
86+
let fn_ptr = Pointer::from(fn_alloc_id);
87+
ScalarMaybeUninit::from_pointer(fn_ptr, &tcx)
88+
}
89+
VtblEntry::MetadataSize => Scalar::from_uint(size, ptr_size).into(),
90+
VtblEntry::MetadataAlign => Scalar::from_uint(align, ptr_size).into(),
91+
VtblEntry::Vacant => continue,
92+
VtblEntry::Method(instance) => {
93+
// Prepare the fn ptr we write into the vtable.
94+
let instance = instance.polymorphize(tcx);
95+
let fn_alloc_id = tcx.create_fn_alloc(instance);
96+
let fn_ptr = Pointer::from(fn_alloc_id);
97+
ScalarMaybeUninit::from_pointer(fn_ptr, &tcx)
98+
}
99+
VtblEntry::TraitVPtr(trait_ref) => {
100+
let super_trait_ref = trait_ref
101+
.map_bound(|trait_ref| ty::ExistentialTraitRef::erase_self_ty(tcx, trait_ref));
102+
let supertrait_alloc_id = tcx.vtable_allocation((ty, Some(super_trait_ref)));
103+
let vptr = Pointer::from(supertrait_alloc_id);
104+
ScalarMaybeUninit::from_pointer(vptr, &tcx)
105+
}
106+
};
107+
vtable
108+
.write_scalar(&tcx, alloc_range(ptr_size * idx, ptr_size), scalar)
109+
.expect("failed to build vtable representation");
126110
}
111+
112+
vtable.mutability = Mutability::Not;
113+
tcx.create_memory_alloc(tcx.intern_const_alloc(vtable))
127114
}

compiler/rustc_query_impl/src/keys.rs

+11
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,17 @@ impl<'tcx> Key for mir::interpret::GlobalId<'tcx> {
7272
}
7373
}
7474

75+
impl<'tcx> Key for (Ty<'tcx>, Option<ty::PolyExistentialTraitRef<'tcx>>) {
76+
#[inline(always)]
77+
fn query_crate_is_local(&self) -> bool {
78+
true
79+
}
80+
81+
fn default_span(&self, _: TyCtxt<'_>) -> Span {
82+
DUMMY_SP
83+
}
84+
}
85+
7586
impl<'tcx> Key for mir::interpret::LitToConstInput<'tcx> {
7687
#[inline(always)]
7788
fn query_crate_is_local(&self) -> bool {
+41
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
// revisions:rpass1 rpass2
2+
3+
// This test case makes sure re-order the methods in a vtable will
4+
// trigger recompilation of codegen units that instantiate it.
5+
//
6+
// See https://github.com/rust-lang/rust/issues/89598
7+
8+
trait Foo {
9+
#[cfg(rpass1)]
10+
fn method1(&self) -> u32;
11+
12+
fn method2(&self) -> u32;
13+
14+
#[cfg(rpass2)]
15+
fn method1(&self) -> u32;
16+
}
17+
18+
impl Foo for u32 {
19+
fn method1(&self) -> u32 { 17 }
20+
fn method2(&self) -> u32 { 42 }
21+
}
22+
23+
fn main() {
24+
// Before #89598 was fixed, the vtable allocation would be cached during
25+
// a MIR optimization pass and then the codegen pass for the main object
26+
// file would not register a dependency on it (because of the missing
27+
// dep-tracking).
28+
//
29+
// In the rpass2 session, the main object file would not be re-compiled,
30+
// thus the mod1::foo(x) call would pass in an outdated vtable, while the
31+
// mod1 object would expect the new, re-ordered vtable, resulting in a
32+
// call to the wrong method.
33+
let x: &dyn Foo = &0u32;
34+
assert_eq!(mod1::foo(x), 17);
35+
}
36+
37+
mod mod1 {
38+
pub(super) fn foo(x: &dyn super::Foo) -> u32 {
39+
x.method1()
40+
}
41+
}

0 commit comments

Comments
 (0)