From f2545fb22554eb1b04528d490c681e583fdc31d0 Mon Sep 17 00:00:00 2001 From: Maybe Waffle Date: Wed, 17 May 2023 12:28:04 +0000 Subject: [PATCH 1/5] Collect VTable stats & add `-Zprint-vtable-sizes` --- compiler/rustc_driver_impl/src/lib.rs | 7 ++ compiler/rustc_interface/src/interface.rs | 1 + compiler/rustc_interface/src/passes.rs | 94 +++++++++++++++++++ compiler/rustc_session/src/code_stats.rs | 44 ++++++++- compiler/rustc_session/src/lib.rs | 2 +- compiler/rustc_session/src/options.rs | 2 + .../rustc_trait_selection/src/traits/mod.rs | 2 +- .../src/traits/vtable.rs | 4 +- 8 files changed, 151 insertions(+), 5 deletions(-) diff --git a/compiler/rustc_driver_impl/src/lib.rs b/compiler/rustc_driver_impl/src/lib.rs index 5b75205442ba1..f8dda23ef9cf6 100644 --- a/compiler/rustc_driver_impl/src/lib.rs +++ b/compiler/rustc_driver_impl/src/lib.rs @@ -430,6 +430,13 @@ fn run_compiler( sess.code_stats.print_type_sizes(); } + if sess.opts.unstable_opts.print_vtable_sizes { + let crate_name = + compiler.session().opts.crate_name.as_deref().unwrap_or(""); + + sess.code_stats.print_vtable_sizes(crate_name); + } + let linker = queries.linker()?; Ok(Some(linker)) })?; diff --git a/compiler/rustc_interface/src/interface.rs b/compiler/rustc_interface/src/interface.rs index 2edc72ba72ea1..5e3ea71f0e768 100644 --- a/compiler/rustc_interface/src/interface.rs +++ b/compiler/rustc_interface/src/interface.rs @@ -333,6 +333,7 @@ pub fn run_compiler(config: Config, f: impl FnOnce(&Compiler) -> R + Se }; let prof = compiler.sess.prof.clone(); + prof.generic_activity("drop_compiler").run(move || drop(compiler)); r }) diff --git a/compiler/rustc_interface/src/passes.rs b/compiler/rustc_interface/src/passes.rs index 83a74742f5b76..be6bd91379021 100644 --- a/compiler/rustc_interface/src/passes.rs +++ b/compiler/rustc_interface/src/passes.rs @@ -24,6 +24,7 @@ use rustc_parse::{parse_crate_from_file, parse_crate_from_source_str, validate_a use rustc_passes::{self, hir_stats, layout_test}; use rustc_plugin_impl as plugin; use rustc_resolve::Resolver; +use rustc_session::code_stats::VTableSizeInfo; use rustc_session::config::{CrateType, Input, OutFileName, OutputFilenames, OutputType}; use rustc_session::cstore::{MetadataLoader, Untracked}; use rustc_session::output::filename_for_input; @@ -866,6 +867,99 @@ fn analysis(tcx: TyCtxt<'_>, (): ()) -> Result<()> { sess.time("check_lint_expectations", || tcx.check_expectations(None)); }); + if sess.opts.unstable_opts.print_vtable_sizes { + let traits = tcx.traits(LOCAL_CRATE); + + for &tr in traits { + if !tcx.check_is_object_safe(tr) { + continue; + } + + let name = ty::print::with_no_trimmed_paths!(tcx.def_path_str(tr)); + + let mut first_dsa = true; + + // Number of vtable entries, if we didn't have upcasting + let mut unupcasted_cost = 0; + // Number of vtable entries needed solely for upcasting + let mut upcast_cost = 0; + + let trait_ref = ty::Binder::dummy(ty::TraitRef::identity(tcx, tr)); + + // A slightly edited version of the code in `rustc_trait_selection::traits::vtable::vtable_entries`, + // that works without self type and just counts number of entries. + // + // Note that this is technically wrong, for traits which have associated types in supertraits: + // + // trait A: AsRef + AsRef<()> { type T; } + // + // Without self type we can't normalize `Self::T`, so we can't know if `AsRef` and + // `AsRef<()>` are the same trait, thus we assume that those are different, and potentially + // over-estimate how many vtable entries there are. + // + // Similarly this is wrong for traits that have methods with possibly-impossible bounds. + // For example: + // + // trait B { fn f(&self) where T: Copy; } + // + // Here `dyn B` will have 4 entries, while `dyn B` will only have 3. + // However, since we don't know `T`, we can't know if `T: Copy` holds or not, + // thus we lean on the bigger side and say it has 4 entries. + traits::vtable::prepare_vtable_segments(tcx, trait_ref, |segment| { + match segment { + traits::vtable::VtblSegment::MetadataDSA => { + // If this is the first dsa, it would be included either way, + // otherwise it's needed for upcasting + if std::mem::take(&mut first_dsa) { + unupcasted_cost += 3; + } else { + upcast_cost += 3; + } + } + + traits::vtable::VtblSegment::TraitOwnEntries { trait_ref, emit_vptr } => { + let existential_trait_ref = trait_ref.map_bound(|trait_ref| { + ty::ExistentialTraitRef::erase_self_ty(tcx, trait_ref) + }); + + // Lookup the shape of vtable for the trait. + let own_existential_entries = + tcx.own_existential_vtable_entries(existential_trait_ref.def_id()); + + let own_entries = own_existential_entries.iter().copied().map(|_def_id| { + // The original code here ignores the method if its predicates are impossible. + // We can't really do that as, for example, all not trivial bounds on generic + // parameters are impossible (since we don't know the parameters...), + // see the comment above. + + 1 + }); + + unupcasted_cost += own_entries.sum::(); + + if emit_vptr { + upcast_cost += 1; + } + } + } + + std::ops::ControlFlow::Continue::(()) + }); + + sess.code_stats.record_vtable_size( + tr, + &name, + VTableSizeInfo { + trait_name: name.clone(), + size_words_without_upcasting: unupcasted_cost, + size_words_with_upcasting: unupcasted_cost + upcast_cost, + difference_words: upcast_cost, + difference_percent: upcast_cost as f64 / unupcasted_cost as f64 * 100., + }, + ) + } + } + Ok(()) } diff --git a/compiler/rustc_session/src/code_stats.rs b/compiler/rustc_session/src/code_stats.rs index 0dfee92f40434..f76263de13f62 100644 --- a/compiler/rustc_session/src/code_stats.rs +++ b/compiler/rustc_session/src/code_stats.rs @@ -1,5 +1,6 @@ -use rustc_data_structures::fx::FxHashSet; +use rustc_data_structures::fx::{FxHashMap, FxHashSet}; use rustc_data_structures::sync::Lock; +use rustc_span::def_id::DefId; use rustc_span::Symbol; use rustc_target::abi::{Align, Size}; use std::cmp; @@ -65,9 +66,18 @@ pub struct TypeSizeInfo { pub variants: Vec, } +pub struct VTableSizeInfo { + pub trait_name: String, + pub size_words_without_upcasting: usize, + pub size_words_with_upcasting: usize, + pub difference_words: usize, + pub difference_percent: f64, +} + #[derive(Default)] pub struct CodeStats { type_sizes: Lock>, + vtable_sizes: Lock>, } impl CodeStats { @@ -101,6 +111,14 @@ impl CodeStats { self.type_sizes.borrow_mut().insert(info); } + pub fn record_vtable_size(&self, trait_did: DefId, trait_name: &str, info: VTableSizeInfo) { + let prev = self.vtable_sizes.lock().insert(trait_did, info); + assert!( + prev.is_none(), + "size of vtable for `{trait_name}` ({trait_did:?}) is already recorded" + ); + } + pub fn print_type_sizes(&self) { let type_sizes = self.type_sizes.borrow(); let mut sorted: Vec<_> = type_sizes.iter().collect(); @@ -196,4 +214,28 @@ impl CodeStats { } } } + + pub fn print_vtable_sizes(&self, crate_name: &str) { + let mut rr = std::mem::take(&mut *self.vtable_sizes.lock()).into_iter().collect::>(); + + rr.sort_by(|(_, stats_a), (_, stats_b)| { + stats_b.difference_percent.total_cmp(&stats_a.difference_percent) + }); + + for ( + _, + VTableSizeInfo { + trait_name, + size_words_without_upcasting, + size_words_with_upcasting, + difference_words, + difference_percent, + }, + ) in rr + { + println!( + 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}" }}"# + ); + } + } } diff --git a/compiler/rustc_session/src/lib.rs b/compiler/rustc_session/src/lib.rs index 590a68c660061..d57aa820fcb47 100644 --- a/compiler/rustc_session/src/lib.rs +++ b/compiler/rustc_session/src/lib.rs @@ -27,7 +27,7 @@ pub use lint::{declare_lint, declare_lint_pass, declare_tool_lint, impl_lint_pas pub use rustc_lint_defs as lint; pub mod parse; -mod code_stats; +pub mod code_stats; #[macro_use] pub mod config; pub mod cstore; diff --git a/compiler/rustc_session/src/options.rs b/compiler/rustc_session/src/options.rs index 201066e395017..b626c721481db 100644 --- a/compiler/rustc_session/src/options.rs +++ b/compiler/rustc_session/src/options.rs @@ -1632,6 +1632,8 @@ options! { "print the result of the monomorphization collection pass"), print_type_sizes: bool = (false, parse_bool, [UNTRACKED], "print layout information for each type encountered (default: no)"), + print_vtable_sizes: bool = (false, parse_bool, [UNTRACKED], + "print size comparison between old and new vtable layouts (default: no)"), proc_macro_backtrace: bool = (false, parse_bool, [UNTRACKED], "show backtraces for panics during proc-macro execution (default: no)"), proc_macro_execution_strategy: ProcMacroExecutionStrategy = (ProcMacroExecutionStrategy::SameThread, diff --git a/compiler/rustc_trait_selection/src/traits/mod.rs b/compiler/rustc_trait_selection/src/traits/mod.rs index c2f94cb638566..ab58684ffd9c4 100644 --- a/compiler/rustc_trait_selection/src/traits/mod.rs +++ b/compiler/rustc_trait_selection/src/traits/mod.rs @@ -21,7 +21,7 @@ mod structural_match; mod structural_normalize; #[cfg_attr(not(bootstrap), allow(hidden_glob_reexports))] mod util; -mod vtable; +pub mod vtable; pub mod wf; use crate::infer::outlives::env::OutlivesEnvironment; diff --git a/compiler/rustc_trait_selection/src/traits/vtable.rs b/compiler/rustc_trait_selection/src/traits/vtable.rs index cc674ceee3d5d..96b8e0b82b6af 100644 --- a/compiler/rustc_trait_selection/src/traits/vtable.rs +++ b/compiler/rustc_trait_selection/src/traits/vtable.rs @@ -15,13 +15,13 @@ use std::fmt::Debug; use std::ops::ControlFlow; #[derive(Clone, Debug)] -pub(super) enum VtblSegment<'tcx> { +pub enum VtblSegment<'tcx> { MetadataDSA, TraitOwnEntries { trait_ref: ty::PolyTraitRef<'tcx>, emit_vptr: bool }, } /// Prepare the segments for a vtable -pub(super) fn prepare_vtable_segments<'tcx, T>( +pub fn prepare_vtable_segments<'tcx, T>( tcx: TyCtxt<'tcx>, trait_ref: ty::PolyTraitRef<'tcx>, mut segment_visitor: impl FnMut(VtblSegment<'tcx>) -> ControlFlow, From 5008a08acf6d3e8e01e6b1768850579da6058aea Mon Sep 17 00:00:00 2001 From: Maybe Waffle Date: Tue, 13 Jun 2023 11:46:40 +0000 Subject: [PATCH 2/5] Simplify code as suggested by the review --- compiler/rustc_interface/src/passes.rs | 21 ++++++--------------- 1 file changed, 6 insertions(+), 15 deletions(-) diff --git a/compiler/rustc_interface/src/passes.rs b/compiler/rustc_interface/src/passes.rs index be6bd91379021..d61f2d0668a29 100644 --- a/compiler/rustc_interface/src/passes.rs +++ b/compiler/rustc_interface/src/passes.rs @@ -918,24 +918,15 @@ fn analysis(tcx: TyCtxt<'_>, (): ()) -> Result<()> { } traits::vtable::VtblSegment::TraitOwnEntries { trait_ref, emit_vptr } => { - let existential_trait_ref = trait_ref.map_bound(|trait_ref| { - ty::ExistentialTraitRef::erase_self_ty(tcx, trait_ref) - }); - // Lookup the shape of vtable for the trait. let own_existential_entries = - tcx.own_existential_vtable_entries(existential_trait_ref.def_id()); - - let own_entries = own_existential_entries.iter().copied().map(|_def_id| { - // The original code here ignores the method if its predicates are impossible. - // We can't really do that as, for example, all not trivial bounds on generic - // parameters are impossible (since we don't know the parameters...), - // see the comment above. - - 1 - }); + tcx.own_existential_vtable_entries(trait_ref.def_id()); - unupcasted_cost += own_entries.sum::(); + // The original code here ignores the method if its predicates are impossible. + // We can't really do that as, for example, all not trivial bounds on generic + // parameters are impossible (since we don't know the parameters...), + // see the comment above. + unupcasted_cost += own_existential_entries.len(); if emit_vptr { upcast_cost += 1; From 8e6a193946eea931c44caecfdd3c3353d036335b Mon Sep 17 00:00:00 2001 From: Maybe Waffle Date: Tue, 13 Jun 2023 12:07:12 +0000 Subject: [PATCH 3/5] =?UTF-8?q?Tweak=20names=20and=20docs=20for=20vtable?= =?UTF-8?q?=C2=A0stats?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- compiler/rustc_interface/src/passes.rs | 22 ++++++----- compiler/rustc_session/src/code_stats.rs | 50 +++++++++++++++--------- 2 files changed, 43 insertions(+), 29 deletions(-) diff --git a/compiler/rustc_interface/src/passes.rs b/compiler/rustc_interface/src/passes.rs index d61f2d0668a29..be2af94961ffa 100644 --- a/compiler/rustc_interface/src/passes.rs +++ b/compiler/rustc_interface/src/passes.rs @@ -880,9 +880,9 @@ fn analysis(tcx: TyCtxt<'_>, (): ()) -> Result<()> { let mut first_dsa = true; // Number of vtable entries, if we didn't have upcasting - let mut unupcasted_cost = 0; + let mut entries_ignoring_upcasting = 0; // Number of vtable entries needed solely for upcasting - let mut upcast_cost = 0; + let mut entries_for_upcasting = 0; let trait_ref = ty::Binder::dummy(ty::TraitRef::identity(tcx, tr)); @@ -911,9 +911,9 @@ fn analysis(tcx: TyCtxt<'_>, (): ()) -> Result<()> { // If this is the first dsa, it would be included either way, // otherwise it's needed for upcasting if std::mem::take(&mut first_dsa) { - unupcasted_cost += 3; + entries_ignoring_upcasting += 3; } else { - upcast_cost += 3; + entries_for_upcasting += 3; } } @@ -926,10 +926,10 @@ fn analysis(tcx: TyCtxt<'_>, (): ()) -> Result<()> { // We can't really do that as, for example, all not trivial bounds on generic // parameters are impossible (since we don't know the parameters...), // see the comment above. - unupcasted_cost += own_existential_entries.len(); + entries_ignoring_upcasting += own_existential_entries.len(); if emit_vptr { - upcast_cost += 1; + entries_for_upcasting += 1; } } } @@ -942,10 +942,12 @@ fn analysis(tcx: TyCtxt<'_>, (): ()) -> Result<()> { &name, VTableSizeInfo { trait_name: name.clone(), - size_words_without_upcasting: unupcasted_cost, - size_words_with_upcasting: unupcasted_cost + upcast_cost, - difference_words: upcast_cost, - difference_percent: upcast_cost as f64 / unupcasted_cost as f64 * 100., + entries: entries_ignoring_upcasting + entries_for_upcasting, + entries_ignoring_upcasting, + entries_for_upcasting, + upcasting_cost_percent: entries_for_upcasting as f64 + / entries_ignoring_upcasting as f64 + * 100., }, ) } diff --git a/compiler/rustc_session/src/code_stats.rs b/compiler/rustc_session/src/code_stats.rs index f76263de13f62..51cac328c3a31 100644 --- a/compiler/rustc_session/src/code_stats.rs +++ b/compiler/rustc_session/src/code_stats.rs @@ -68,10 +68,21 @@ pub struct TypeSizeInfo { pub struct VTableSizeInfo { pub trait_name: String, - pub size_words_without_upcasting: usize, - pub size_words_with_upcasting: usize, - pub difference_words: usize, - pub difference_percent: f64, + + /// Number of entries in a vtable with the current algorithm + /// (i.e. with upcasting). + pub entries: usize, + + /// Number of entries in a vtable, as-if we did not have trait upcasting. + pub entries_ignoring_upcasting: usize, + + /// Number of entries in a vtable needed solely for upcasting + /// (i.e. `entries - entries_ignoring_upcasting`). + pub entries_for_upcasting: usize, + + /// Cost of having upcasting in % relative to the number of entries without + /// upcasting (i.e. `entries_for_upcasting / entries_ignoring_upcasting * 100%`). + pub upcasting_cost_percent: f64, } #[derive(Default)] @@ -216,25 +227,26 @@ impl CodeStats { } pub fn print_vtable_sizes(&self, crate_name: &str) { - let mut rr = std::mem::take(&mut *self.vtable_sizes.lock()).into_iter().collect::>(); - - rr.sort_by(|(_, stats_a), (_, stats_b)| { - stats_b.difference_percent.total_cmp(&stats_a.difference_percent) + let mut infos = std::mem::take(&mut *self.vtable_sizes.lock()) + .into_iter() + .map(|(_did, stats)| stats) + .collect::>(); + + // Sort by the cost % in reverse order (from biggest to smallest) + infos.sort_by(|a, b| { + a.upcasting_cost_percent.total_cmp(&b.upcasting_cost_percent).reverse() }); - for ( - _, - VTableSizeInfo { - trait_name, - size_words_without_upcasting, - size_words_with_upcasting, - difference_words, - difference_percent, - }, - ) in rr + for VTableSizeInfo { + trait_name, + entries, + entries_ignoring_upcasting, + entries_for_upcasting, + upcasting_cost_percent, + } in infos { println!( - 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}" }}"# + r#"print-vtable-sizes {{ "crate_name": "{crate_name}", "trait_name": "{trait_name}", "entries": "{entries}", "entries_ignoring_upcasting": "{entries_ignoring_upcasting}", "entries_for_upcasting": "{entries_for_upcasting}", "upcasting_cost_percent": "{upcasting_cost_percent}" }}"# ); } } From dc0fba02383c6248b8a9dfc780c6448eac706a8f Mon Sep 17 00:00:00 2001 From: Maybe Waffle Date: Tue, 13 Jun 2023 12:39:59 +0000 Subject: [PATCH 4/5] Tweak the sort of vtable sizes --- compiler/rustc_session/src/code_stats.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/compiler/rustc_session/src/code_stats.rs b/compiler/rustc_session/src/code_stats.rs index 51cac328c3a31..cabe1c96bf795 100644 --- a/compiler/rustc_session/src/code_stats.rs +++ b/compiler/rustc_session/src/code_stats.rs @@ -232,9 +232,13 @@ impl CodeStats { .map(|(_did, stats)| stats) .collect::>(); - // Sort by the cost % in reverse order (from biggest to smallest) + // Primary sort: cost % in reverse order (from largest to smallest) + // Secondary sort: trait_name infos.sort_by(|a, b| { - a.upcasting_cost_percent.total_cmp(&b.upcasting_cost_percent).reverse() + a.upcasting_cost_percent + .total_cmp(&b.upcasting_cost_percent) + .reverse() + .then_with(|| a.trait_name.cmp(&b.trait_name)) }); for VTableSizeInfo { From af4631ad6e16679243a36487431afde477f7d552 Mon Sep 17 00:00:00 2001 From: Maybe Waffle Date: Tue, 13 Jun 2023 12:40:12 +0000 Subject: [PATCH 5/5] Add a test for `-Zprint-vtable-sizes` --- tests/ui/traits/object/print_vtable_sizes.rs | 61 +++++++++++++++++++ .../traits/object/print_vtable_sizes.stdout | 11 ++++ 2 files changed, 72 insertions(+) create mode 100644 tests/ui/traits/object/print_vtable_sizes.rs create mode 100644 tests/ui/traits/object/print_vtable_sizes.stdout diff --git a/tests/ui/traits/object/print_vtable_sizes.rs b/tests/ui/traits/object/print_vtable_sizes.rs new file mode 100644 index 0000000000000..5656094990be6 --- /dev/null +++ b/tests/ui/traits/object/print_vtable_sizes.rs @@ -0,0 +1,61 @@ +// check-pass +// compile-flags: -Z print-vtable-sizes +#![crate_type = "lib"] + +trait A: AsRef<[T::V]> + AsMut<[T::V]> {} + +trait B: AsRef + AsRef + AsRef + AsRef {} + +trait C { + fn x() {} // not object safe, shouldn't be reported +} + +// This ideally should not have any upcasting cost, +// but currently does due to a bug +trait D: Send + Sync + help::MarkerWithSuper {} + +// This can't have no cost without reordering, +// because `Super::f`. +trait E: help::MarkerWithSuper + Send + Sync {} + +trait F { + fn a(&self); + fn b(&self); + fn c(&self); + + fn d() -> Self + where + Self: Sized; +} + +trait G: AsRef + AsRef + help::MarkerWithSuper { + fn a(&self); + fn b(&self); + fn c(&self); + fn d(&self); + fn e(&self); + + fn f() -> Self + where + Self: Sized; +} + +// Traits with the same name +const _: () = { + trait S {} +}; +const _: () = { + trait S {} +}; + +mod help { + pub trait V { + type V; + } + + pub trait MarkerWithSuper: Super {} + + pub trait Super { + fn f(&self); + } +} diff --git a/tests/ui/traits/object/print_vtable_sizes.stdout b/tests/ui/traits/object/print_vtable_sizes.stdout new file mode 100644 index 0000000000000..3ba650bc36045 --- /dev/null +++ b/tests/ui/traits/object/print_vtable_sizes.stdout @@ -0,0 +1,11 @@ +print-vtable-sizes { "crate_name": "", "trait_name": "D", "entries": "7", "entries_ignoring_upcasting": "4", "entries_for_upcasting": "3", "upcasting_cost_percent": "75" } +print-vtable-sizes { "crate_name": "", "trait_name": "E", "entries": "6", "entries_ignoring_upcasting": "4", "entries_for_upcasting": "2", "upcasting_cost_percent": "50" } +print-vtable-sizes { "crate_name": "", "trait_name": "G", "entries": "14", "entries_ignoring_upcasting": "11", "entries_for_upcasting": "3", "upcasting_cost_percent": "27.27272727272727" } +print-vtable-sizes { "crate_name": "", "trait_name": "A", "entries": "6", "entries_ignoring_upcasting": "5", "entries_for_upcasting": "1", "upcasting_cost_percent": "20" } +print-vtable-sizes { "crate_name": "", "trait_name": "B", "entries": "4", "entries_ignoring_upcasting": "4", "entries_for_upcasting": "0", "upcasting_cost_percent": "0" } +print-vtable-sizes { "crate_name": "", "trait_name": "F", "entries": "6", "entries_ignoring_upcasting": "6", "entries_for_upcasting": "0", "upcasting_cost_percent": "0" } +print-vtable-sizes { "crate_name": "", "trait_name": "_::S", "entries": "3", "entries_ignoring_upcasting": "3", "entries_for_upcasting": "0", "upcasting_cost_percent": "0" } +print-vtable-sizes { "crate_name": "", "trait_name": "_::S", "entries": "3", "entries_ignoring_upcasting": "3", "entries_for_upcasting": "0", "upcasting_cost_percent": "0" } +print-vtable-sizes { "crate_name": "", "trait_name": "help::MarkerWithSuper", "entries": "4", "entries_ignoring_upcasting": "4", "entries_for_upcasting": "0", "upcasting_cost_percent": "0" } +print-vtable-sizes { "crate_name": "", "trait_name": "help::Super", "entries": "4", "entries_ignoring_upcasting": "4", "entries_for_upcasting": "0", "upcasting_cost_percent": "0" } +print-vtable-sizes { "crate_name": "", "trait_name": "help::V", "entries": "3", "entries_ignoring_upcasting": "3", "entries_for_upcasting": "0", "upcasting_cost_percent": "0" }