Skip to content

Commit 781a090

Browse files
Increase vtable layout size
This improves LLVM's codegen by allowing vtable loads to be hoisted out of loops (as just one example).
1 parent e82c861 commit 781a090

File tree

3 files changed

+131
-71
lines changed

3 files changed

+131
-71
lines changed

compiler/rustc_middle/src/ty/layout.rs

+7-18
Original file line numberDiff line numberDiff line change
@@ -791,25 +791,14 @@ where
791791
});
792792
}
793793

794-
let mk_dyn_vtable = || {
794+
let mk_dyn_vtable = |principal: Option<ty::PolyExistentialTraitRef<'tcx>>| {
795+
let min_count = ty::vtable_min_entries(tcx, principal);
795796
Ty::new_imm_ref(
796797
tcx,
797798
tcx.lifetimes.re_static,
798-
Ty::new_array(tcx, tcx.types.usize, 3),
799+
// FIXME: properly type (e.g. usize and fn pointers) the fields.
800+
Ty::new_array(tcx, tcx.types.usize, min_count.try_into().unwrap()),
799801
)
800-
/* FIXME: use actual fn pointers
801-
Warning: naively computing the number of entries in the
802-
vtable by counting the methods on the trait + methods on
803-
all parent traits does not work, because some methods can
804-
be not object safe and thus excluded from the vtable.
805-
Increase this counter if you tried to implement this but
806-
failed to do it without duplicating a lot of code from
807-
other places in the compiler: 2
808-
Ty::new_tup(tcx,&[
809-
Ty::new_array(tcx,tcx.types.usize, 3),
810-
Ty::new_array(tcx,Option<fn()>),
811-
])
812-
*/
813802
};
814803

815804
let metadata = if let Some(metadata_def_id) = tcx.lang_items().metadata_type()
@@ -828,16 +817,16 @@ where
828817
// `std::mem::uninitialized::<&dyn Trait>()`, for example.
829818
if let ty::Adt(def, args) = metadata.kind()
830819
&& Some(def.did()) == tcx.lang_items().dyn_metadata()
831-
&& args.type_at(0).is_trait()
820+
&& let ty::Dynamic(data, _, ty::Dyn) = args.type_at(0).kind()
832821
{
833-
mk_dyn_vtable()
822+
mk_dyn_vtable(data.principal())
834823
} else {
835824
metadata
836825
}
837826
} else {
838827
match tcx.struct_tail_erasing_lifetimes(pointee, cx.param_env()).kind() {
839828
ty::Slice(_) | ty::Str => tcx.types.usize,
840-
ty::Dynamic(_, _, ty::Dyn) => mk_dyn_vtable(),
829+
ty::Dynamic(data, _, ty::Dyn) => mk_dyn_vtable(data.principal()),
841830
_ => bug!("TyAndLayout::field({:?}): not applicable", this),
842831
}
843832
};

compiler/rustc_middle/src/ty/vtable.rs

+64
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ use std::fmt;
33
use crate::mir::interpret::{alloc_range, AllocId, Allocation, Pointer, Scalar};
44
use crate::ty::{self, Instance, PolyTraitRef, Ty, TyCtxt};
55
use rustc_ast::Mutability;
6+
use rustc_data_structures::fx::FxHashSet;
7+
use rustc_hir::def_id::DefId;
68
use rustc_macros::HashStable;
79

810
#[derive(Clone, Copy, PartialEq, HashStable)]
@@ -46,6 +48,65 @@ pub const COMMON_VTABLE_ENTRIES_DROPINPLACE: usize = 0;
4648
pub const COMMON_VTABLE_ENTRIES_SIZE: usize = 1;
4749
pub const COMMON_VTABLE_ENTRIES_ALIGN: usize = 2;
4850

51+
// FIXME: This is duplicating equivalent code in compiler/rustc_trait_selection/src/traits/util.rs
52+
// But that is a downstream crate, and this code is pretty simple. Probably OK for now.
53+
struct SupertraitDefIds<'tcx> {
54+
tcx: TyCtxt<'tcx>,
55+
stack: Vec<DefId>,
56+
visited: FxHashSet<DefId>,
57+
}
58+
59+
fn supertrait_def_ids(tcx: TyCtxt<'_>, trait_def_id: DefId) -> SupertraitDefIds<'_> {
60+
SupertraitDefIds {
61+
tcx,
62+
stack: vec![trait_def_id],
63+
visited: Some(trait_def_id).into_iter().collect(),
64+
}
65+
}
66+
67+
impl Iterator for SupertraitDefIds<'_> {
68+
type Item = DefId;
69+
70+
fn next(&mut self) -> Option<DefId> {
71+
let def_id = self.stack.pop()?;
72+
let predicates = self.tcx.super_predicates_of(def_id);
73+
let visited = &mut self.visited;
74+
self.stack.extend(
75+
predicates
76+
.predicates
77+
.iter()
78+
.filter_map(|(pred, _)| pred.as_trait_clause())
79+
.map(|trait_ref| trait_ref.def_id())
80+
.filter(|&super_def_id| visited.insert(super_def_id)),
81+
);
82+
Some(def_id)
83+
}
84+
}
85+
86+
// Note that we don't have access to a self type here, this has to be purely based on the trait (and
87+
// supertrait) definitions. That means we can't call into the same vtable_entries code since that
88+
// returns a specific instantiation (e.g., with Vacant slots when bounds aren't satisfied). The goal
89+
// here is to do a best-effort approximation without duplicating a lot of code.
90+
//
91+
// This function is used in layout computation for e.g. &dyn Trait, so it's critical that this
92+
// function is an accurate approximation. We verify this when actually computing the vtable below.
93+
pub(crate) fn vtable_min_entries<'tcx>(
94+
tcx: TyCtxt<'tcx>,
95+
trait_ref: Option<ty::PolyExistentialTraitRef<'tcx>>,
96+
) -> usize {
97+
let mut count = TyCtxt::COMMON_VTABLE_ENTRIES.len();
98+
let Some(trait_ref) = trait_ref else {
99+
return count;
100+
};
101+
102+
// This includes self in supertraits.
103+
for def_id in supertrait_def_ids(tcx, trait_ref.def_id()) {
104+
count += tcx.own_existential_vtable_entries(def_id).len();
105+
}
106+
107+
count
108+
}
109+
49110
/// Retrieves an allocation that represents the contents of a vtable.
50111
/// Since this is a query, allocations are cached and not duplicated.
51112
pub(super) fn vtable_allocation_provider<'tcx>(
@@ -63,6 +124,9 @@ pub(super) fn vtable_allocation_provider<'tcx>(
63124
TyCtxt::COMMON_VTABLE_ENTRIES
64125
};
65126

127+
// This confirms that the layout computation for &dyn Trait has an accurate sizing.
128+
assert!(vtable_entries.len() >= vtable_min_entries(tcx, poly_trait_ref));
129+
66130
let layout = tcx
67131
.layout_of(ty::ParamEnv::reveal_all().and(ty))
68132
.expect("failed to build vtable representation");

compiler/rustc_trait_selection/src/traits/object_safety.rs

+60-53
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ use rustc_middle::ty::{ToPredicate, TypeVisitableExt};
2525
use rustc_session::lint::builtin::WHERE_CLAUSES_OBJECT_SAFETY;
2626
use rustc_span::symbol::Symbol;
2727
use rustc_span::Span;
28+
use rustc_target::abi::Abi;
2829
use smallvec::SmallVec;
2930

3031
use std::iter;
@@ -144,6 +145,14 @@ fn object_safety_violations_for_trait(
144145
violations.push(ObjectSafetyViolation::SupertraitNonLifetimeBinder(spans));
145146
}
146147

148+
if violations.is_empty() {
149+
for item in tcx.associated_items(trait_def_id).in_definition_order() {
150+
if let ty::AssocKind::Fn = item.kind {
151+
check_receiver_correct(tcx, trait_def_id, *item);
152+
}
153+
}
154+
}
155+
147156
debug!(
148157
"object_safety_violations_for_trait(trait_def_id={:?}) = {:?}",
149158
trait_def_id, violations
@@ -497,59 +506,8 @@ fn virtual_call_violations_for_method<'tcx>(
497506
};
498507
errors.push(MethodViolationCode::UndispatchableReceiver(span));
499508
} else {
500-
// Do sanity check to make sure the receiver actually has the layout of a pointer.
501-
502-
use rustc_target::abi::Abi;
503-
504-
let param_env = tcx.param_env(method.def_id);
505-
506-
let abi_of_ty = |ty: Ty<'tcx>| -> Option<Abi> {
507-
match tcx.layout_of(param_env.and(ty)) {
508-
Ok(layout) => Some(layout.abi),
509-
Err(err) => {
510-
// #78372
511-
tcx.dcx().span_delayed_bug(
512-
tcx.def_span(method.def_id),
513-
format!("error: {err}\n while computing layout for type {ty:?}"),
514-
);
515-
None
516-
}
517-
}
518-
};
519-
520-
// e.g., `Rc<()>`
521-
let unit_receiver_ty =
522-
receiver_for_self_ty(tcx, receiver_ty, tcx.types.unit, method.def_id);
523-
524-
match abi_of_ty(unit_receiver_ty) {
525-
Some(Abi::Scalar(..)) => (),
526-
abi => {
527-
tcx.dcx().span_delayed_bug(
528-
tcx.def_span(method.def_id),
529-
format!(
530-
"receiver when `Self = ()` should have a Scalar ABI; found {abi:?}"
531-
),
532-
);
533-
}
534-
}
535-
536-
let trait_object_ty = object_ty_for_trait(tcx, trait_def_id, tcx.lifetimes.re_static);
537-
538-
// e.g., `Rc<dyn Trait>`
539-
let trait_object_receiver =
540-
receiver_for_self_ty(tcx, receiver_ty, trait_object_ty, method.def_id);
541-
542-
match abi_of_ty(trait_object_receiver) {
543-
Some(Abi::ScalarPair(..)) => (),
544-
abi => {
545-
tcx.dcx().span_delayed_bug(
546-
tcx.def_span(method.def_id),
547-
format!(
548-
"receiver when `Self = {trait_object_ty}` should have a ScalarPair ABI; found {abi:?}"
549-
),
550-
);
551-
}
552-
}
509+
// We confirm that the `receiver_is_dispatchable` is accurate later,
510+
// see `check_receiver_correct`. It should be kept in sync with this code.
553511
}
554512
}
555513

@@ -610,6 +568,55 @@ fn virtual_call_violations_for_method<'tcx>(
610568
errors
611569
}
612570

571+
/// This code checks that `receiver_is_dispatchable` is correctly implemented.
572+
///
573+
/// This check is outlined from the object safety check to avoid cycles with
574+
/// layout computation, which relies on knowing whether methods are object safe.
575+
pub fn check_receiver_correct<'tcx>(tcx: TyCtxt<'tcx>, trait_def_id: DefId, method: ty::AssocItem) {
576+
if !is_vtable_safe_method(tcx, trait_def_id, method) {
577+
return;
578+
}
579+
580+
let method_def_id = method.def_id;
581+
let sig = tcx.fn_sig(method_def_id).instantiate_identity();
582+
let param_env = tcx.param_env(method_def_id);
583+
let receiver_ty = tcx.liberate_late_bound_regions(method_def_id, sig.input(0));
584+
585+
if receiver_ty == tcx.types.self_param {
586+
// Assumed OK, may change later if unsized_locals permits `self: Self` as dispatchable.
587+
return;
588+
}
589+
590+
// e.g., `Rc<()>`
591+
let unit_receiver_ty = receiver_for_self_ty(tcx, receiver_ty, tcx.types.unit, method_def_id);
592+
match tcx.layout_of(param_env.and(unit_receiver_ty)).map(|l| l.abi) {
593+
Ok(Abi::Scalar(..)) => (),
594+
abi => {
595+
tcx.dcx().span_delayed_bug(
596+
tcx.def_span(method_def_id),
597+
format!("receiver {unit_receiver_ty:?} when `Self = ()` should have a Scalar ABI; found {abi:?}"),
598+
);
599+
}
600+
}
601+
602+
let trait_object_ty = object_ty_for_trait(tcx, trait_def_id, tcx.lifetimes.re_static);
603+
604+
// e.g., `Rc<dyn Trait>`
605+
let trait_object_receiver =
606+
receiver_for_self_ty(tcx, receiver_ty, trait_object_ty, method_def_id);
607+
match tcx.layout_of(param_env.and(trait_object_receiver)).map(|l| l.abi) {
608+
Ok(Abi::ScalarPair(..)) => (),
609+
abi => {
610+
tcx.dcx().span_delayed_bug(
611+
tcx.def_span(method_def_id),
612+
format!(
613+
"receiver {trait_object_receiver:?} when `Self = {trait_object_ty}` should have a ScalarPair ABI; found {abi:?}"
614+
),
615+
);
616+
}
617+
}
618+
}
619+
613620
/// Performs a type instantiation to produce the version of `receiver_ty` when `Self = self_ty`.
614621
/// For example, for `receiver_ty = Rc<Self>` and `self_ty = Foo`, returns `Rc<Foo>`.
615622
fn receiver_for_self_ty<'tcx>(

0 commit comments

Comments
 (0)