Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Collect VTable stats & add -Zprint-vtable-sizes #112400

Merged
merged 5 commits into from
Jun 14, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions compiler/rustc_driver_impl/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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("<UNKNOWN_CRATE>");

sess.code_stats.print_vtable_sizes(crate_name);
}

let linker = queries.linker()?;
Ok(Some(linker))
})?;
Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_interface/src/interface.rs
Original file line number Diff line number Diff line change
Expand Up @@ -333,6 +333,7 @@ pub fn run_compiler<R: Send>(config: Config, f: impl FnOnce(&Compiler) -> R + Se
};

let prof = compiler.sess.prof.clone();

prof.generic_activity("drop_compiler").run(move || drop(compiler));
r
})
Expand Down
87 changes: 87 additions & 0 deletions compiler/rustc_interface/src/passes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -866,6 +867,92 @@ 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 {
WaffleLapkin marked this conversation as resolved.
Show resolved Hide resolved
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 entries_ignoring_upcasting = 0;
// Number of vtable entries needed solely for upcasting
let mut entries_for_upcasting = 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<Self::T> + AsRef<()> { type T; }
//
// Without self type we can't normalize `Self::T`, so we can't know if `AsRef<Self::T>` 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<T> { fn f(&self) where T: Copy; }
//
// Here `dyn B<u8>` will have 4 entries, while `dyn B<String>` 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) {
entries_ignoring_upcasting += 3;
} else {
entries_for_upcasting += 3;
}
}

traits::vtable::VtblSegment::TraitOwnEntries { trait_ref, emit_vptr } => {
// Lookup the shape of vtable for the trait.
let own_existential_entries =
tcx.own_existential_vtable_entries(trait_ref.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.
entries_ignoring_upcasting += own_existential_entries.len();

if emit_vptr {
entries_for_upcasting += 1;
}
}
}

std::ops::ControlFlow::Continue::<std::convert::Infallible>(())
});

sess.code_stats.record_vtable_size(
tr,
&name,
VTableSizeInfo {
trait_name: name.clone(),
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.,
},
)
}
}

Ok(())
}

Expand Down
60 changes: 59 additions & 1 deletion compiler/rustc_session/src/code_stats.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -65,9 +66,29 @@ pub struct TypeSizeInfo {
pub variants: Vec<VariantInfo>,
}

pub struct VTableSizeInfo {
pub trait_name: String,

/// 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)]
pub struct CodeStats {
type_sizes: Lock<FxHashSet<TypeSizeInfo>>,
vtable_sizes: Lock<FxHashMap<DefId, VTableSizeInfo>>,
}

impl CodeStats {
Expand Down Expand Up @@ -101,6 +122,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();
Expand Down Expand Up @@ -196,4 +225,33 @@ impl CodeStats {
}
}
}

pub fn print_vtable_sizes(&self, crate_name: &str) {
let mut infos = std::mem::take(&mut *self.vtable_sizes.lock())
.into_iter()
.map(|(_did, stats)| stats)
.collect::<Vec<_>>();

// 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()
.then_with(|| a.trait_name.cmp(&b.trait_name))
});

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}", "entries": "{entries}", "entries_ignoring_upcasting": "{entries_ignoring_upcasting}", "entries_for_upcasting": "{entries_for_upcasting}", "upcasting_cost_percent": "{upcasting_cost_percent}" }}"#
);
}
}
}
2 changes: 1 addition & 1 deletion compiler/rustc_session/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
2 changes: 2 additions & 0 deletions compiler/rustc_session/src/options.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
2 changes: 1 addition & 1 deletion compiler/rustc_trait_selection/src/traits/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
4 changes: 2 additions & 2 deletions compiler/rustc_trait_selection/src/traits/vtable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<T>,
Expand Down
61 changes: 61 additions & 0 deletions tests/ui/traits/object/print_vtable_sizes.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
// check-pass
// compile-flags: -Z print-vtable-sizes
#![crate_type = "lib"]

trait A<T: help::V>: AsRef<[T::V]> + AsMut<[T::V]> {}

trait B<T>: AsRef<T> + AsRef<T> + AsRef<T> + AsRef<T> {}

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<u8> + AsRef<u16> + 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);
}
}
11 changes: 11 additions & 0 deletions tests/ui/traits/object/print_vtable_sizes.stdout
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
print-vtable-sizes { "crate_name": "<UNKNOWN_CRATE>", "trait_name": "D", "entries": "7", "entries_ignoring_upcasting": "4", "entries_for_upcasting": "3", "upcasting_cost_percent": "75" }
print-vtable-sizes { "crate_name": "<UNKNOWN_CRATE>", "trait_name": "E", "entries": "6", "entries_ignoring_upcasting": "4", "entries_for_upcasting": "2", "upcasting_cost_percent": "50" }
print-vtable-sizes { "crate_name": "<UNKNOWN_CRATE>", "trait_name": "G", "entries": "14", "entries_ignoring_upcasting": "11", "entries_for_upcasting": "3", "upcasting_cost_percent": "27.27272727272727" }
print-vtable-sizes { "crate_name": "<UNKNOWN_CRATE>", "trait_name": "A", "entries": "6", "entries_ignoring_upcasting": "5", "entries_for_upcasting": "1", "upcasting_cost_percent": "20" }
print-vtable-sizes { "crate_name": "<UNKNOWN_CRATE>", "trait_name": "B", "entries": "4", "entries_ignoring_upcasting": "4", "entries_for_upcasting": "0", "upcasting_cost_percent": "0" }
print-vtable-sizes { "crate_name": "<UNKNOWN_CRATE>", "trait_name": "F", "entries": "6", "entries_ignoring_upcasting": "6", "entries_for_upcasting": "0", "upcasting_cost_percent": "0" }
print-vtable-sizes { "crate_name": "<UNKNOWN_CRATE>", "trait_name": "_::S", "entries": "3", "entries_ignoring_upcasting": "3", "entries_for_upcasting": "0", "upcasting_cost_percent": "0" }
print-vtable-sizes { "crate_name": "<UNKNOWN_CRATE>", "trait_name": "_::S", "entries": "3", "entries_ignoring_upcasting": "3", "entries_for_upcasting": "0", "upcasting_cost_percent": "0" }
print-vtable-sizes { "crate_name": "<UNKNOWN_CRATE>", "trait_name": "help::MarkerWithSuper", "entries": "4", "entries_ignoring_upcasting": "4", "entries_for_upcasting": "0", "upcasting_cost_percent": "0" }
print-vtable-sizes { "crate_name": "<UNKNOWN_CRATE>", "trait_name": "help::Super", "entries": "4", "entries_ignoring_upcasting": "4", "entries_for_upcasting": "0", "upcasting_cost_percent": "0" }
print-vtable-sizes { "crate_name": "<UNKNOWN_CRATE>", "trait_name": "help::V", "entries": "3", "entries_ignoring_upcasting": "3", "entries_for_upcasting": "0", "upcasting_cost_percent": "0" }