Skip to content

Commit 7cbce96

Browse files
avanhattdanielsn
authored andcommitted
Generics in dyn trait fix: use vtable index to resolve functions (rust-lang#352)
Use vtable index passed at call site to resolve functions Co-authored-by: Daniel Schwartz-Narbonne <[email protected]>
1 parent a9a7bf6 commit 7cbce96

File tree

9 files changed

+199
-19
lines changed

9 files changed

+199
-19
lines changed

compiler/rustc_codegen_llvm/src/gotoc/metadata.rs

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -92,16 +92,15 @@ impl<'tcx> GotocCtx<'tcx> {
9292
format!("{}::global::{}::", self.crate_name(), c)
9393
}
9494

95-
/// For the vtable field name, we need exactly the dyn trait name and the function
96-
/// name. The table itself already is scoped by the object type.
97-
/// Example: ::Shape::vol
98-
/// Note: this is _not_ the same name for top-level entry into the symbol table,
99-
/// which does need more crate/type information and uses the full symbol_name(...)
100-
pub fn vtable_field_name(&self, def_id: DefId) -> String {
101-
// `to_string_no_crate_verbose` is from Rust proper, we use it here because it
102-
// always includes the dyn trait name and function name.
103-
// Tracking a less brittle solution here: https://github.com/model-checking/rmc/issues/187
104-
self.tcx.def_path(def_id).to_string_no_crate_verbose()
95+
/// The name for the struct field on a vtable for a given function. Because generic
96+
/// functions can share the same name, we need to use the index of the entry in the
97+
/// vtable. This is the same index that will be passed in virtual function calls as
98+
/// InstanceDef::Virtual(def_id, idx). We could use solely the index as a key into
99+
/// the vtable struct, but we add the trait and function names for debugging
100+
/// readability.
101+
/// Example: 3_Shape::vol
102+
pub fn vtable_field_name(&self, def_id: DefId, idx: usize) -> String {
103+
format!("{}_{}", idx, with_no_trimmed_paths(|| self.tcx.def_path_str(def_id)))
105104
}
106105

107106
/// A human readable name in Rust for reference, should not be used as a key.

compiler/rustc_codegen_llvm/src/gotoc/rvalue.rs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -705,8 +705,9 @@ impl<'tcx> GotocCtx<'tcx> {
705705
def_id: DefId,
706706
substs: ty::subst::SubstsRef<'tcx>,
707707
t: Ty<'tcx>,
708+
idx: usize,
708709
) -> Expr {
709-
let vtable_field_name = self.vtable_field_name(def_id);
710+
let vtable_field_name = self.vtable_field_name(def_id, idx);
710711
let vtable_type_name = aggr_name(&self.vtable_name(t));
711712
let field_type = self
712713
.symbol_table
@@ -849,15 +850,16 @@ impl<'tcx> GotocCtx<'tcx> {
849850

850851
let vtable_fields: Vec<Expr> = vtable_entries
851852
.iter()
852-
.filter_map(|entry| match entry {
853+
.enumerate()
854+
.filter_map(|(idx, entry)| match entry {
853855
VtblEntry::MetadataDropInPlace => {
854856
Some(ctx.codegen_vtable_drop_in_place(&src_mir_type, trait_type))
855857
}
856858
VtblEntry::MetadataSize => Some(vt_size.clone()),
857859
VtblEntry::MetadataAlign => Some(vt_align.clone()),
858860
VtblEntry::Vacant => None,
859861
VtblEntry::Method(def_id, substs) => {
860-
Some(ctx.codegen_vtable_method_field(*def_id, substs, trait_type))
862+
Some(ctx.codegen_vtable_method_field(*def_id, substs, trait_type, idx))
861863
}
862864
})
863865
.collect();

compiler/rustc_codegen_llvm/src/gotoc/statement.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -283,7 +283,7 @@ impl<'tcx> GotocCtx<'tcx> {
283283
return Stmt::goto(self.current_fn().find_label(&target), Location::none());
284284
}
285285
// Handle a virtual function call via a vtable lookup
286-
InstanceDef::Virtual(def_id, _) => {
286+
InstanceDef::Virtual(def_id, idx) => {
287287
// We must have at least one argument, and the first one
288288
// should be a fat pointer for the trait
289289
let trait_fat_ptr = fargs[0].to_owned();
@@ -294,6 +294,7 @@ impl<'tcx> GotocCtx<'tcx> {
294294
self.codegen_virtual_funcall(
295295
trait_fat_ptr,
296296
def_id,
297+
idx,
297298
&p,
298299
&mut fargs,
299300
loc.clone(),
@@ -340,11 +341,12 @@ impl<'tcx> GotocCtx<'tcx> {
340341
&mut self,
341342
trait_fat_ptr: Expr,
342343
def_id: DefId,
344+
idx: usize,
343345
place: &Place<'tcx>,
344346
fargs: &mut Vec<Expr>,
345347
loc: Location,
346348
) -> Vec<Stmt> {
347-
let vtable_field_name = self.vtable_field_name(def_id);
349+
let vtable_field_name = self.vtable_field_name(def_id, idx);
348350

349351
// Now that we have all the stuff we need, we can actually build the dynamic call
350352
// If the original call was of the form

compiler/rustc_codegen_llvm/src/gotoc/typ.rs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,7 @@ impl<'tcx> GotocCtx<'tcx> {
119119
&mut self,
120120
def_id: DefId,
121121
substs: SubstsRef<'tcx>,
122+
idx: usize,
122123
) -> DatatypeComponent {
123124
let instance = Instance::resolve(self.tcx, ty::ParamEnv::reveal_all(), def_id, substs)
124125
.unwrap()
@@ -130,8 +131,8 @@ impl<'tcx> GotocCtx<'tcx> {
130131
// gives an Irep Pointer object for the signature
131132
let fnptr = self.codegen_dynamic_function_sig(sig).to_pointer();
132133

133-
// vtable field name, i.e., ::Shape::vol
134-
let vtable_field_name = self.vtable_field_name(def_id);
134+
// vtable field name, i.e., 3_Shape::vol (idx_Trait::method)
135+
let vtable_field_name = self.vtable_field_name(def_id, idx);
135136

136137
let ins_ty = instance.ty(self.tcx, ty::ParamEnv::reveal_all());
137138
let _layout = self.layout_of(ins_ty);
@@ -195,9 +196,10 @@ impl<'tcx> GotocCtx<'tcx> {
195196
.vtable_entries(poly)
196197
.iter()
197198
.cloned()
198-
.filter_map(|entry| match entry {
199+
.enumerate()
200+
.filter_map(|(idx, entry)| match entry {
199201
VtblEntry::Method(def_id, substs) => {
200-
Some(self.trait_method_vtable_field_type(def_id, substs))
202+
Some(self.trait_method_vtable_field_type(def_id, substs, idx))
201203
}
202204
VtblEntry::MetadataDropInPlace
203205
| VtblEntry::MetadataSize
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0 OR MIT
3+
4+
// This test checks that we can handle potential naming conflicts when
5+
// generic types give the same Trait::function name pairs.
6+
7+
trait Foo<T> {
8+
fn method(&self, t: T) -> T;
9+
}
10+
11+
trait Bar: Foo<u32> + Foo<i32> {}
12+
13+
impl<T> Foo<T> for () {
14+
fn method(&self, t: T) -> T {
15+
t
16+
}
17+
}
18+
19+
impl Bar for () {}
20+
21+
fn main() {
22+
let b: &dyn Bar = &();
23+
// The vtable for b will now have two Foo::method entries,
24+
// one for Foo<u32> and one for Foo<i32>.
25+
let result = <dyn Bar as Foo<u32>>::method(b, 22_u32);
26+
assert!(result == 22_u32);
27+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0 OR MIT
3+
4+
// This test checks that we can handle potential naming conflicts when
5+
// generic types give the same Trait::function name pairs. Test the
6+
// wrong result for this _fail test.
7+
8+
include!("../../rmc-prelude.rs");
9+
10+
trait Foo<T> {
11+
fn method(&self, t: T) -> T;
12+
}
13+
14+
trait Bar: Foo<u32> + Foo<i32> {}
15+
16+
impl<T> Foo<T> for () {
17+
fn method(&self, t: T) -> T {
18+
t
19+
}
20+
}
21+
22+
impl Bar for () {}
23+
24+
fn main() {
25+
let b: &dyn Bar = &();
26+
// The vtable for b will now have two Foo::method entries,
27+
// one for Foo<u32> and one for Foo<i32>.
28+
let result = <dyn Bar as Foo<u32>>::method(b, 22_u32);
29+
__VERIFIER_expect_fail(result == 0, "Wrong result");
30+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0 OR MIT
3+
4+
// This test checks that we can handle potential naming conflicts when
5+
// generic types give the same Trait::function name pairs. This test
6+
// gives different concrete impls for i32 and u32.
7+
8+
trait Foo<T> {
9+
fn method(&self, t: T) -> T;
10+
}
11+
12+
trait Bar: Foo<u32> + Foo<i32> {}
13+
14+
impl Foo<i32> for () {
15+
fn method(&self, t: i32) -> i32 {
16+
0
17+
}
18+
}
19+
20+
impl Foo<u32> for () {
21+
fn method(&self, t: u32) -> u32 {
22+
t
23+
}
24+
}
25+
26+
impl Bar for () {}
27+
28+
fn main() {
29+
let b: &dyn Bar = &();
30+
// The vtable for b will now have two Foo::method entries,
31+
// one for Foo<u32> and one for Foo<i32>.
32+
let result = <dyn Bar as Foo<u32>>::method(b, 22_u32);
33+
assert!(result == 22_u32);
34+
35+
let result = <dyn Bar as Foo<i32>>::method(b, 22_i32);
36+
assert!(result == 0);
37+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0 OR MIT
3+
4+
// This test checks that we can handle potential naming conflicts when
5+
// generic types give the same Trait::function name pairs. This test
6+
// gives different concrete impls for i32 and u32. This _fail test
7+
// expects the wrong result.
8+
9+
include!("../../rmc-prelude.rs");
10+
11+
trait Foo<T> {
12+
fn method(&self, t: T) -> T;
13+
}
14+
15+
trait Bar: Foo<u32> + Foo<i32> {}
16+
17+
impl Foo<i32> for () {
18+
fn method(&self, t: i32) -> i32 {
19+
0
20+
}
21+
}
22+
23+
impl Foo<u32> for () {
24+
fn method(&self, t: u32) -> u32 {
25+
t
26+
}
27+
}
28+
29+
impl Bar for () {}
30+
31+
fn main() {
32+
let b: &dyn Bar = &();
33+
// The vtable for b will now have two Foo::method entries,
34+
// one for Foo<u32> and one for Foo<i32>.
35+
let result = <dyn Bar as Foo<u32>>::method(b, 22_u32);
36+
__VERIFIER_expect_fail(result == 0, "Wrong function");
37+
38+
let result = <dyn Bar as Foo<i32>>::method(b, 22_i32);
39+
__VERIFIER_expect_fail(result == 22_i32, "Wrong function");
40+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0 OR MIT
3+
4+
// This test checks that we can handle potential naming conflicts when
5+
// generic types give the same Trait::function name pairs, even when
6+
// non-object-safe methods force the vtable to have VtblEntry::Vacant
7+
// positions
8+
9+
trait Foo<T> {
10+
// Non-object-safe method first, so the vtable has
11+
// a vacant spot before the important method
12+
fn new() -> Self
13+
where
14+
Self: Sized;
15+
16+
fn method(&self, t: T) -> T;
17+
}
18+
19+
trait Bar: Foo<u32> + Foo<i32> {}
20+
21+
impl<T> Foo<T> for () {
22+
fn new() -> Self {
23+
unimplemented!()
24+
}
25+
26+
fn method(&self, t: T) -> T {
27+
t
28+
}
29+
}
30+
31+
impl Bar for () {}
32+
33+
fn main() {
34+
let b: &dyn Bar = &();
35+
// The vtable for b will now have two Foo::method entries,
36+
// one for Foo<u32> and one for Foo<i32>. Both follow the
37+
// vacant vtable entries for Foo<u32>::new and Foo<i32>::new
38+
// which are not object safe.
39+
let result = <dyn Bar as Foo<u32>>::method(b, 22_u32);
40+
assert!(result == 22_u32);
41+
}

0 commit comments

Comments
 (0)