Skip to content

Commit f2545fb

Browse files
committed
Collect VTable stats & add -Zprint-vtable-sizes
1 parent cb882fa commit f2545fb

File tree

8 files changed

+151
-5
lines changed

8 files changed

+151
-5
lines changed

compiler/rustc_driver_impl/src/lib.rs

+7
Original file line numberDiff line numberDiff line change
@@ -430,6 +430,13 @@ fn run_compiler(
430430
sess.code_stats.print_type_sizes();
431431
}
432432

433+
if sess.opts.unstable_opts.print_vtable_sizes {
434+
let crate_name =
435+
compiler.session().opts.crate_name.as_deref().unwrap_or("<UNKNOWN_CRATE>");
436+
437+
sess.code_stats.print_vtable_sizes(crate_name);
438+
}
439+
433440
let linker = queries.linker()?;
434441
Ok(Some(linker))
435442
})?;

compiler/rustc_interface/src/interface.rs

+1
Original file line numberDiff line numberDiff line change
@@ -333,6 +333,7 @@ pub fn run_compiler<R: Send>(config: Config, f: impl FnOnce(&Compiler) -> R + Se
333333
};
334334

335335
let prof = compiler.sess.prof.clone();
336+
336337
prof.generic_activity("drop_compiler").run(move || drop(compiler));
337338
r
338339
})

compiler/rustc_interface/src/passes.rs

+94
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ use rustc_parse::{parse_crate_from_file, parse_crate_from_source_str, validate_a
2424
use rustc_passes::{self, hir_stats, layout_test};
2525
use rustc_plugin_impl as plugin;
2626
use rustc_resolve::Resolver;
27+
use rustc_session::code_stats::VTableSizeInfo;
2728
use rustc_session::config::{CrateType, Input, OutFileName, OutputFilenames, OutputType};
2829
use rustc_session::cstore::{MetadataLoader, Untracked};
2930
use rustc_session::output::filename_for_input;
@@ -866,6 +867,99 @@ fn analysis(tcx: TyCtxt<'_>, (): ()) -> Result<()> {
866867
sess.time("check_lint_expectations", || tcx.check_expectations(None));
867868
});
868869

870+
if sess.opts.unstable_opts.print_vtable_sizes {
871+
let traits = tcx.traits(LOCAL_CRATE);
872+
873+
for &tr in traits {
874+
if !tcx.check_is_object_safe(tr) {
875+
continue;
876+
}
877+
878+
let name = ty::print::with_no_trimmed_paths!(tcx.def_path_str(tr));
879+
880+
let mut first_dsa = true;
881+
882+
// Number of vtable entries, if we didn't have upcasting
883+
let mut unupcasted_cost = 0;
884+
// Number of vtable entries needed solely for upcasting
885+
let mut upcast_cost = 0;
886+
887+
let trait_ref = ty::Binder::dummy(ty::TraitRef::identity(tcx, tr));
888+
889+
// A slightly edited version of the code in `rustc_trait_selection::traits::vtable::vtable_entries`,
890+
// that works without self type and just counts number of entries.
891+
//
892+
// Note that this is technically wrong, for traits which have associated types in supertraits:
893+
//
894+
// trait A: AsRef<Self::T> + AsRef<()> { type T; }
895+
//
896+
// Without self type we can't normalize `Self::T`, so we can't know if `AsRef<Self::T>` and
897+
// `AsRef<()>` are the same trait, thus we assume that those are different, and potentially
898+
// over-estimate how many vtable entries there are.
899+
//
900+
// Similarly this is wrong for traits that have methods with possibly-impossible bounds.
901+
// For example:
902+
//
903+
// trait B<T> { fn f(&self) where T: Copy; }
904+
//
905+
// Here `dyn B<u8>` will have 4 entries, while `dyn B<String>` will only have 3.
906+
// However, since we don't know `T`, we can't know if `T: Copy` holds or not,
907+
// thus we lean on the bigger side and say it has 4 entries.
908+
traits::vtable::prepare_vtable_segments(tcx, trait_ref, |segment| {
909+
match segment {
910+
traits::vtable::VtblSegment::MetadataDSA => {
911+
// If this is the first dsa, it would be included either way,
912+
// otherwise it's needed for upcasting
913+
if std::mem::take(&mut first_dsa) {
914+
unupcasted_cost += 3;
915+
} else {
916+
upcast_cost += 3;
917+
}
918+
}
919+
920+
traits::vtable::VtblSegment::TraitOwnEntries { trait_ref, emit_vptr } => {
921+
let existential_trait_ref = trait_ref.map_bound(|trait_ref| {
922+
ty::ExistentialTraitRef::erase_self_ty(tcx, trait_ref)
923+
});
924+
925+
// Lookup the shape of vtable for the trait.
926+
let own_existential_entries =
927+
tcx.own_existential_vtable_entries(existential_trait_ref.def_id());
928+
929+
let own_entries = own_existential_entries.iter().copied().map(|_def_id| {
930+
// The original code here ignores the method if its predicates are impossible.
931+
// We can't really do that as, for example, all not trivial bounds on generic
932+
// parameters are impossible (since we don't know the parameters...),
933+
// see the comment above.
934+
935+
1
936+
});
937+
938+
unupcasted_cost += own_entries.sum::<usize>();
939+
940+
if emit_vptr {
941+
upcast_cost += 1;
942+
}
943+
}
944+
}
945+
946+
std::ops::ControlFlow::Continue::<std::convert::Infallible>(())
947+
});
948+
949+
sess.code_stats.record_vtable_size(
950+
tr,
951+
&name,
952+
VTableSizeInfo {
953+
trait_name: name.clone(),
954+
size_words_without_upcasting: unupcasted_cost,
955+
size_words_with_upcasting: unupcasted_cost + upcast_cost,
956+
difference_words: upcast_cost,
957+
difference_percent: upcast_cost as f64 / unupcasted_cost as f64 * 100.,
958+
},
959+
)
960+
}
961+
}
962+
869963
Ok(())
870964
}
871965

compiler/rustc_session/src/code_stats.rs

+43-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
use rustc_data_structures::fx::FxHashSet;
1+
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
22
use rustc_data_structures::sync::Lock;
3+
use rustc_span::def_id::DefId;
34
use rustc_span::Symbol;
45
use rustc_target::abi::{Align, Size};
56
use std::cmp;
@@ -65,9 +66,18 @@ pub struct TypeSizeInfo {
6566
pub variants: Vec<VariantInfo>,
6667
}
6768

69+
pub struct VTableSizeInfo {
70+
pub trait_name: String,
71+
pub size_words_without_upcasting: usize,
72+
pub size_words_with_upcasting: usize,
73+
pub difference_words: usize,
74+
pub difference_percent: f64,
75+
}
76+
6877
#[derive(Default)]
6978
pub struct CodeStats {
7079
type_sizes: Lock<FxHashSet<TypeSizeInfo>>,
80+
vtable_sizes: Lock<FxHashMap<DefId, VTableSizeInfo>>,
7181
}
7282

7383
impl CodeStats {
@@ -101,6 +111,14 @@ impl CodeStats {
101111
self.type_sizes.borrow_mut().insert(info);
102112
}
103113

114+
pub fn record_vtable_size(&self, trait_did: DefId, trait_name: &str, info: VTableSizeInfo) {
115+
let prev = self.vtable_sizes.lock().insert(trait_did, info);
116+
assert!(
117+
prev.is_none(),
118+
"size of vtable for `{trait_name}` ({trait_did:?}) is already recorded"
119+
);
120+
}
121+
104122
pub fn print_type_sizes(&self) {
105123
let type_sizes = self.type_sizes.borrow();
106124
let mut sorted: Vec<_> = type_sizes.iter().collect();
@@ -196,4 +214,28 @@ impl CodeStats {
196214
}
197215
}
198216
}
217+
218+
pub fn print_vtable_sizes(&self, crate_name: &str) {
219+
let mut rr = std::mem::take(&mut *self.vtable_sizes.lock()).into_iter().collect::<Vec<_>>();
220+
221+
rr.sort_by(|(_, stats_a), (_, stats_b)| {
222+
stats_b.difference_percent.total_cmp(&stats_a.difference_percent)
223+
});
224+
225+
for (
226+
_,
227+
VTableSizeInfo {
228+
trait_name,
229+
size_words_without_upcasting,
230+
size_words_with_upcasting,
231+
difference_words,
232+
difference_percent,
233+
},
234+
) in rr
235+
{
236+
println!(
237+
r#"print-vtable-sizes {{ "crate_name": "{crate_name}", "trait_name": "{trait_name}", "size_unupcastable_words": "{size_words_without_upcasting}", "size_upcastable_words": "{size_words_with_upcasting}", diff: "{difference_words}", diff_p: "{difference_percent}" }}"#
238+
);
239+
}
240+
}
199241
}

compiler/rustc_session/src/lib.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ pub use lint::{declare_lint, declare_lint_pass, declare_tool_lint, impl_lint_pas
2727
pub use rustc_lint_defs as lint;
2828
pub mod parse;
2929

30-
mod code_stats;
30+
pub mod code_stats;
3131
#[macro_use]
3232
pub mod config;
3333
pub mod cstore;

compiler/rustc_session/src/options.rs

+2
Original file line numberDiff line numberDiff line change
@@ -1632,6 +1632,8 @@ options! {
16321632
"print the result of the monomorphization collection pass"),
16331633
print_type_sizes: bool = (false, parse_bool, [UNTRACKED],
16341634
"print layout information for each type encountered (default: no)"),
1635+
print_vtable_sizes: bool = (false, parse_bool, [UNTRACKED],
1636+
"print size comparison between old and new vtable layouts (default: no)"),
16351637
proc_macro_backtrace: bool = (false, parse_bool, [UNTRACKED],
16361638
"show backtraces for panics during proc-macro execution (default: no)"),
16371639
proc_macro_execution_strategy: ProcMacroExecutionStrategy = (ProcMacroExecutionStrategy::SameThread,

compiler/rustc_trait_selection/src/traits/mod.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ mod structural_match;
2121
mod structural_normalize;
2222
#[cfg_attr(not(bootstrap), allow(hidden_glob_reexports))]
2323
mod util;
24-
mod vtable;
24+
pub mod vtable;
2525
pub mod wf;
2626

2727
use crate::infer::outlives::env::OutlivesEnvironment;

compiler/rustc_trait_selection/src/traits/vtable.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,13 @@ use std::fmt::Debug;
1515
use std::ops::ControlFlow;
1616

1717
#[derive(Clone, Debug)]
18-
pub(super) enum VtblSegment<'tcx> {
18+
pub enum VtblSegment<'tcx> {
1919
MetadataDSA,
2020
TraitOwnEntries { trait_ref: ty::PolyTraitRef<'tcx>, emit_vptr: bool },
2121
}
2222

2323
/// Prepare the segments for a vtable
24-
pub(super) fn prepare_vtable_segments<'tcx, T>(
24+
pub fn prepare_vtable_segments<'tcx, T>(
2525
tcx: TyCtxt<'tcx>,
2626
trait_ref: ty::PolyTraitRef<'tcx>,
2727
mut segment_visitor: impl FnMut(VtblSegment<'tcx>) -> ControlFlow<T>,

0 commit comments

Comments
 (0)